Page MenuHomePhabricator

D15324.diff
No OneTemporary

D15324.diff

diff --git a/resources/sql/autopatches/20160221.almanac.7.namespacen.sql b/resources/sql/autopatches/20160221.almanac.7.namespacen.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20160221.almanac.7.namespacen.sql
@@ -0,0 +1,7 @@
+CREATE TABLE {$NAMESPACE}_almanac.almanac_namespacename_ngrams (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ objectID INT UNSIGNED NOT NULL,
+ ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT},
+ KEY `key_object` (objectID),
+ KEY `key_ngram` (ngram, objectID)
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
diff --git a/resources/sql/autopatches/20160221.almanac.8.namespace.sql b/resources/sql/autopatches/20160221.almanac.8.namespace.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20160221.almanac.8.namespace.sql
@@ -0,0 +1,14 @@
+CREATE TABLE {$NAMESPACE}_almanac.almanac_namespace (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ phid VARBINARY(64) NOT NULL,
+ name VARCHAR(128) NOT NULL COLLATE {$COLLATE_TEXT},
+ nameIndex BINARY(12) NOT NULL,
+ mailKey BINARY(20) NOT NULL,
+ viewPolicy VARBINARY(64) NOT NULL,
+ editPolicy VARBINARY(64) NOT NULL,
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL,
+ UNIQUE KEY `key_phid` (phid),
+ UNIQUE KEY `key_nameindex` (nameIndex),
+ KEY `key_name` (name)
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
diff --git a/resources/sql/autopatches/20160221.almanac.9.namespacex.sql b/resources/sql/autopatches/20160221.almanac.9.namespacex.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20160221.almanac.9.namespacex.sql
@@ -0,0 +1,19 @@
+CREATE TABLE {$NAMESPACE}_almanac.almanac_namespacetransaction (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ phid VARBINARY(64) NOT NULL,
+ authorPHID VARBINARY(64) NOT NULL,
+ objectPHID VARBINARY(64) NOT NULL,
+ viewPolicy VARBINARY(64) NOT NULL,
+ editPolicy VARBINARY(64) NOT NULL,
+ commentPHID VARBINARY(64) DEFAULT NULL,
+ commentVersion INT UNSIGNED NOT NULL,
+ transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL,
+ oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+ newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+ contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+ metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL,
+ UNIQUE KEY `key_phid` (`phid`),
+ KEY `key_object` (`objectPHID`)
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -28,6 +28,7 @@
'AlmanacCoreCustomField' => 'applications/almanac/customfield/AlmanacCoreCustomField.php',
'AlmanacCreateClusterServicesCapability' => 'applications/almanac/capability/AlmanacCreateClusterServicesCapability.php',
'AlmanacCreateDevicesCapability' => 'applications/almanac/capability/AlmanacCreateDevicesCapability.php',
+ 'AlmanacCreateNamespacesCapability' => 'applications/almanac/capability/AlmanacCreateNamespacesCapability.php',
'AlmanacCreateNetworksCapability' => 'applications/almanac/capability/AlmanacCreateNetworksCapability.php',
'AlmanacCreateServicesCapability' => 'applications/almanac/capability/AlmanacCreateServicesCapability.php',
'AlmanacCustomField' => 'applications/almanac/customfield/AlmanacCustomField.php',
@@ -61,6 +62,19 @@
'AlmanacManagementWorkflow' => 'applications/almanac/management/AlmanacManagementWorkflow.php',
'AlmanacNames' => 'applications/almanac/util/AlmanacNames.php',
'AlmanacNamesTestCase' => 'applications/almanac/util/__tests__/AlmanacNamesTestCase.php',
+ 'AlmanacNamespace' => 'applications/almanac/storage/AlmanacNamespace.php',
+ 'AlmanacNamespaceController' => 'applications/almanac/controller/AlmanacNamespaceController.php',
+ 'AlmanacNamespaceEditController' => 'applications/almanac/controller/AlmanacNamespaceEditController.php',
+ 'AlmanacNamespaceEditEngine' => 'applications/almanac/editor/AlmanacNamespaceEditEngine.php',
+ 'AlmanacNamespaceEditor' => 'applications/almanac/editor/AlmanacNamespaceEditor.php',
+ 'AlmanacNamespaceListController' => 'applications/almanac/controller/AlmanacNamespaceListController.php',
+ 'AlmanacNamespaceNameNgrams' => 'applications/almanac/storage/AlmanacNamespaceNameNgrams.php',
+ 'AlmanacNamespacePHIDType' => 'applications/almanac/phid/AlmanacNamespacePHIDType.php',
+ 'AlmanacNamespaceQuery' => 'applications/almanac/query/AlmanacNamespaceQuery.php',
+ 'AlmanacNamespaceSearchEngine' => 'applications/almanac/query/AlmanacNamespaceSearchEngine.php',
+ 'AlmanacNamespaceTransaction' => 'applications/almanac/storage/AlmanacNamespaceTransaction.php',
+ 'AlmanacNamespaceTransactionQuery' => 'applications/almanac/query/AlmanacNamespaceTransactionQuery.php',
+ 'AlmanacNamespaceViewController' => 'applications/almanac/controller/AlmanacNamespaceViewController.php',
'AlmanacNetwork' => 'applications/almanac/storage/AlmanacNetwork.php',
'AlmanacNetworkController' => 'applications/almanac/controller/AlmanacNetworkController.php',
'AlmanacNetworkEditController' => 'applications/almanac/controller/AlmanacNetworkEditController.php',
@@ -4000,6 +4014,7 @@
),
'AlmanacCreateClusterServicesCapability' => 'PhabricatorPolicyCapability',
'AlmanacCreateDevicesCapability' => 'PhabricatorPolicyCapability',
+ 'AlmanacCreateNamespacesCapability' => 'PhabricatorPolicyCapability',
'AlmanacCreateNetworksCapability' => 'PhabricatorPolicyCapability',
'AlmanacCreateServicesCapability' => 'PhabricatorPolicyCapability',
'AlmanacCustomField' => 'PhabricatorCustomField',
@@ -4047,6 +4062,28 @@
'AlmanacManagementWorkflow' => 'PhabricatorManagementWorkflow',
'AlmanacNames' => 'Phobject',
'AlmanacNamesTestCase' => 'PhabricatorTestCase',
+ 'AlmanacNamespace' => array(
+ 'AlmanacDAO',
+ 'PhabricatorPolicyInterface',
+ 'PhabricatorCustomFieldInterface',
+ 'PhabricatorApplicationTransactionInterface',
+ 'PhabricatorProjectInterface',
+ 'AlmanacPropertyInterface',
+ 'PhabricatorDestructibleInterface',
+ 'PhabricatorNgramsInterface',
+ ),
+ 'AlmanacNamespaceController' => 'AlmanacController',
+ 'AlmanacNamespaceEditController' => 'AlmanacController',
+ 'AlmanacNamespaceEditEngine' => 'PhabricatorEditEngine',
+ 'AlmanacNamespaceEditor' => 'PhabricatorApplicationTransactionEditor',
+ 'AlmanacNamespaceListController' => 'AlmanacNamespaceController',
+ 'AlmanacNamespaceNameNgrams' => 'PhabricatorSearchNgrams',
+ 'AlmanacNamespacePHIDType' => 'PhabricatorPHIDType',
+ 'AlmanacNamespaceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'AlmanacNamespaceSearchEngine' => 'PhabricatorApplicationSearchEngine',
+ 'AlmanacNamespaceTransaction' => 'PhabricatorApplicationTransaction',
+ 'AlmanacNamespaceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
+ 'AlmanacNamespaceViewController' => 'AlmanacNamespaceController',
'AlmanacNetwork' => array(
'AlmanacDAO',
'PhabricatorApplicationTransactionInterface',
diff --git a/src/applications/almanac/application/PhabricatorAlmanacApplication.php b/src/applications/almanac/application/PhabricatorAlmanacApplication.php
--- a/src/applications/almanac/application/PhabricatorAlmanacApplication.php
+++ b/src/applications/almanac/application/PhabricatorAlmanacApplication.php
@@ -29,7 +29,7 @@
public function getHelpDocumentationArticles(PhabricatorUser $viewer) {
return array(
array(
- 'name' => pht('Alamanac User Guide'),
+ 'name' => pht('Almanac User Guide'),
'href' => PhabricatorEnv::getDoclink('Almanac User Guide'),
),
);
@@ -44,12 +44,12 @@
'/almanac/' => array(
'' => 'AlmanacConsoleController',
'service/' => array(
- '(?:query/(?P<queryKey>[^/]+)/)?' => 'AlmanacServiceListController',
+ $this->getQueryRoutePattern() => 'AlmanacServiceListController',
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacServiceEditController',
'view/(?P<name>[^/]+)/' => 'AlmanacServiceViewController',
),
'device/' => array(
- '(?:query/(?P<queryKey>[^/]+)/)?' => 'AlmanacDeviceListController',
+ $this->getQueryRoutePattern() => 'AlmanacDeviceListController',
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacDeviceEditController',
'view/(?P<name>[^/]+)/' => 'AlmanacDeviceViewController',
),
@@ -61,7 +61,7 @@
'(?P<id>\d+)/' => 'AlmanacBindingViewController',
),
'network/' => array(
- '(?:query/(?P<queryKey>[^/]+)/)?' => 'AlmanacNetworkListController',
+ $this->getQueryRoutePattern() => 'AlmanacNetworkListController',
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacNetworkEditController',
'(?P<id>\d+)/' => 'AlmanacNetworkViewController',
),
@@ -69,6 +69,12 @@
'edit/' => 'AlmanacPropertyEditController',
'delete/' => 'AlmanacPropertyDeleteController',
),
+ 'namespace/' => array(
+ $this->getQueryRoutePattern() => 'AlmanacNamespaceListController',
+ $this->getEditRoutePattern('edit/')
+ => 'AlmanacNamespaceEditController',
+ '(?P<id>\d+)/' => 'AlmanacNamespaceViewController',
+ ),
),
);
}
@@ -84,6 +90,9 @@
AlmanacCreateNetworksCapability::CAPABILITY => array(
'default' => PhabricatorPolicies::POLICY_ADMIN,
),
+ AlmanacCreateNamespacesCapability::CAPABILITY => array(
+ 'default' => PhabricatorPolicies::POLICY_ADMIN,
+ ),
AlmanacCreateClusterServicesCapability::CAPABILITY => array(
'default' => PhabricatorPolicies::POLICY_ADMIN,
),
diff --git a/src/applications/almanac/capability/AlmanacCreateNamespacesCapability.php b/src/applications/almanac/capability/AlmanacCreateNamespacesCapability.php
new file mode 100644
--- /dev/null
+++ b/src/applications/almanac/capability/AlmanacCreateNamespacesCapability.php
@@ -0,0 +1,16 @@
+<?php
+
+final class AlmanacCreateNamespacesCapability
+ extends PhabricatorPolicyCapability {
+
+ const CAPABILITY = 'almanac.namespaces';
+
+ public function getCapabilityName() {
+ return pht('Can Create Namespaces');
+ }
+
+ public function describeCapabilityRejection() {
+ return pht('You do not have permission to create Almanac namespaces.');
+ }
+
+}
diff --git a/src/applications/almanac/controller/AlmanacConsoleController.php b/src/applications/almanac/controller/AlmanacConsoleController.php
--- a/src/applications/almanac/controller/AlmanacConsoleController.php
+++ b/src/applications/almanac/controller/AlmanacConsoleController.php
@@ -14,24 +14,50 @@
$menu->addItem(
id(new PHUIObjectItemView())
- ->setHeader(pht('Services'))
- ->setHref($this->getApplicationURI('service/'))
- ->setIcon('fa-plug')
- ->addAttribute(pht('Manage Almanac services.')));
-
- $menu->addItem(
- id(new PHUIObjectItemView())
->setHeader(pht('Devices'))
->setHref($this->getApplicationURI('device/'))
->setIcon('fa-server')
- ->addAttribute(pht('Manage Almanac devices.')));
+ ->addAttribute(
+ pht(
+ 'Create an inventory of physical and virtual hosts and '.
+ 'devices.')));
+
+ $menu->addItem(
+ id(new PHUIObjectItemView())
+ ->setHeader(pht('Services'))
+ ->setHref($this->getApplicationURI('service/'))
+ ->setIcon('fa-plug')
+ ->addAttribute(
+ pht(
+ 'Create and update services, and map them to interfaces on '.
+ 'devices.')));
$menu->addItem(
id(new PHUIObjectItemView())
->setHeader(pht('Networks'))
->setHref($this->getApplicationURI('network/'))
->setIcon('fa-globe')
- ->addAttribute(pht('Manage Almanac networks.')));
+ ->addAttribute(
+ pht(
+ 'Manage public and private networks.')));
+
+ $menu->addItem(
+ id(new PHUIObjectItemView())
+ ->setHeader(pht('Namespaces'))
+ ->setHref($this->getApplicationURI('namespace/'))
+ ->setIcon('fa-asterisk')
+ ->addAttribute(
+ pht('Control who can create new named services and devices.')));
+
+ $docs_uri = PhabricatorEnv::getDoclink(
+ 'Almanac User Guide');
+
+ $menu->addItem(
+ id(new PHUIObjectItemView())
+ ->setHeader(pht('Documentation'))
+ ->setHref($docs_uri)
+ ->setIcon('fa-book')
+ ->addAttribute(pht('Browse documentation for Almanac.')));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Console'));
diff --git a/src/applications/almanac/controller/AlmanacNamespaceController.php b/src/applications/almanac/controller/AlmanacNamespaceController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/almanac/controller/AlmanacNamespaceController.php
@@ -0,0 +1,14 @@
+<?php
+
+abstract class AlmanacNamespaceController extends AlmanacController {
+
+ protected function buildApplicationCrumbs() {
+ $crumbs = parent::buildApplicationCrumbs();
+
+ $list_uri = $this->getApplicationURI('namespace/');
+ $crumbs->addTextCrumb(pht('Namespaces'), $list_uri);
+
+ return $crumbs;
+ }
+
+}
diff --git a/src/applications/almanac/controller/AlmanacNamespaceEditController.php b/src/applications/almanac/controller/AlmanacNamespaceEditController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/almanac/controller/AlmanacNamespaceEditController.php
@@ -0,0 +1,11 @@
+<?php
+
+final class AlmanacNamespaceEditController extends AlmanacController {
+
+ public function handleRequest(AphrontRequest $request) {
+ return id(new AlmanacNamespaceEditEngine())
+ ->setController($this)
+ ->buildResponse();
+ }
+
+}
diff --git a/src/applications/almanac/controller/AlmanacNamespaceListController.php b/src/applications/almanac/controller/AlmanacNamespaceListController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/almanac/controller/AlmanacNamespaceListController.php
@@ -0,0 +1,26 @@
+<?php
+
+final class AlmanacNamespaceListController
+ extends AlmanacNamespaceController {
+
+ public function shouldAllowPublic() {
+ return true;
+ }
+
+ public function handleRequest(AphrontRequest $request) {
+ return id(new AlmanacNamespaceSearchEngine())
+ ->setController($this)
+ ->buildResponse();
+ }
+
+ protected function buildApplicationCrumbs() {
+ $crumbs = parent::buildApplicationCrumbs();
+
+ id(new AlmanacNamespaceEditEngine())
+ ->setViewer($this->getViewer())
+ ->addActionToCrumbs($crumbs);
+
+ return $crumbs;
+ }
+
+}
diff --git a/src/applications/almanac/controller/AlmanacNamespaceViewController.php b/src/applications/almanac/controller/AlmanacNamespaceViewController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/almanac/controller/AlmanacNamespaceViewController.php
@@ -0,0 +1,87 @@
+<?php
+
+final class AlmanacNamespaceViewController
+ extends AlmanacNamespaceController {
+
+ public function shouldAllowPublic() {
+ return true;
+ }
+
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $request->getViewer();
+
+ $id = $request->getURIData('id');
+ $namespace = id(new AlmanacNamespaceQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($id))
+ ->executeOne();
+ if (!$namespace) {
+ return new Aphront404Response();
+ }
+
+ $title = pht('Namespace %s', $namespace->getName());
+
+ $property_list = $this->buildPropertyList($namespace);
+ $action_list = $this->buildActionList($namespace);
+ $property_list->setActionList($action_list);
+
+ $header = id(new PHUIHeaderView())
+ ->setUser($viewer)
+ ->setHeader($namespace->getName())
+ ->setPolicyObject($namespace);
+
+ $box = id(new PHUIObjectBoxView())
+ ->setHeader($header)
+ ->addPropertyList($property_list);
+
+ $crumbs = $this->buildApplicationCrumbs();
+ $crumbs->addTextCrumb($namespace->getName());
+
+ $timeline = $this->buildTransactionTimeline(
+ $namespace,
+ new AlmanacNamespaceTransactionQuery());
+ $timeline->setShouldTerminate(true);
+
+ return $this->newPage()
+ ->setTitle($title)
+ ->setCrumbs($crumbs)
+ ->appendChild(
+ array(
+ $box,
+ $timeline,
+ ));
+ }
+
+ private function buildPropertyList(AlmanacNamespace $namespace) {
+ $viewer = $this->getViewer();
+
+ $properties = id(new PHUIPropertyListView())
+ ->setUser($viewer);
+
+ return $properties;
+ }
+
+ private function buildActionList(AlmanacNamespace $namespace) {
+ $viewer = $this->getViewer();
+ $id = $namespace->getID();
+
+ $can_edit = PhabricatorPolicyFilter::hasCapability(
+ $viewer,
+ $namespace,
+ PhabricatorPolicyCapability::CAN_EDIT);
+
+ $actions = id(new PhabricatorActionListView())
+ ->setUser($viewer);
+
+ $actions->addAction(
+ id(new PhabricatorActionView())
+ ->setIcon('fa-pencil')
+ ->setName(pht('Edit Namespace'))
+ ->setHref($this->getApplicationURI("namespace/edit/{$id}/"))
+ ->setWorkflow(!$can_edit)
+ ->setDisabled(!$can_edit));
+
+ return $actions;
+ }
+
+}
diff --git a/src/applications/almanac/controller/AlmanacPropertyEditController.php b/src/applications/almanac/controller/AlmanacPropertyEditController.php
--- a/src/applications/almanac/controller/AlmanacPropertyEditController.php
+++ b/src/applications/almanac/controller/AlmanacPropertyEditController.php
@@ -55,7 +55,7 @@
} else {
$caught = null;
try {
- AlmanacNames::validateServiceOrDeviceName($name);
+ AlmanacNames::validateName($name);
} catch (Exception $ex) {
$caught = $ex;
}
@@ -92,7 +92,7 @@
// Make sure property key is appropriate.
// TODO: It would be cleaner to put this safety check in the Editor.
- AlmanacNames::validateServiceOrDeviceName($property_key);
+ AlmanacNames::validateName($property_key);
// If we're adding a new property, put a placeholder on the object so
// that we can build a CustomField for it.
diff --git a/src/applications/almanac/editor/AlmanacDeviceEditor.php b/src/applications/almanac/editor/AlmanacDeviceEditor.php
--- a/src/applications/almanac/editor/AlmanacDeviceEditor.php
+++ b/src/applications/almanac/editor/AlmanacDeviceEditor.php
@@ -136,7 +136,7 @@
$name = $xaction->getNewValue();
try {
- AlmanacNames::validateServiceOrDeviceName($name);
+ AlmanacNames::validateName($name);
} catch (Exception $ex) {
$message = $ex->getMessage();
}
diff --git a/src/applications/almanac/editor/AlmanacNamespaceEditEngine.php b/src/applications/almanac/editor/AlmanacNamespaceEditEngine.php
new file mode 100644
--- /dev/null
+++ b/src/applications/almanac/editor/AlmanacNamespaceEditEngine.php
@@ -0,0 +1,86 @@
+<?php
+
+final class AlmanacNamespaceEditEngine
+ extends PhabricatorEditEngine {
+
+ const ENGINECONST = 'almanac.namespace';
+
+ public function isEngineConfigurable() {
+ return false;
+ }
+
+ public function getEngineName() {
+ return pht('Almanac Namespaces');
+ }
+
+ public function getSummaryHeader() {
+ return pht('Edit Almanac Namespace Configurations');
+ }
+
+ public function getSummaryText() {
+ return pht('This engine is used to edit Almanac namespaces.');
+ }
+
+ public function getEngineApplicationClass() {
+ return 'PhabricatorAlmanacApplication';
+ }
+
+ protected function newEditableObject() {
+ return AlmanacNamespace::initializeNewNamespace();
+ }
+
+ protected function newObjectQuery() {
+ return new AlmanacNamespaceQuery();
+ }
+
+ protected function getObjectCreateTitleText($object) {
+ return pht('Create Namespace');
+ }
+
+ protected function getObjectCreateButtonText($object) {
+ return pht('Create Namespace');
+ }
+
+ protected function getObjectEditTitleText($object) {
+ return pht('Edit Namespace: %s', $object->getName());
+ }
+
+ protected function getObjectEditShortText($object) {
+ return pht('Edit Namespace');
+ }
+
+ protected function getObjectCreateShortText() {
+ return pht('Create Namespace');
+ }
+
+ protected function getEditorURI() {
+ return '/almanac/namespace/edit/';
+ }
+
+ protected function getObjectCreateCancelURI($object) {
+ return '/almanac/namespace/';
+ }
+
+ protected function getObjectViewURI($object) {
+ $id = $object->getID();
+ return "/almanac/namespace/{$id}/";
+ }
+
+ protected function getCreateNewObjectPolicy() {
+ return $this->getApplication()->getPolicy(
+ AlmanacCreateNamespacesCapability::CAPABILITY);
+ }
+
+ protected function buildCustomEditFields($object) {
+ return array(
+ id(new PhabricatorTextEditField())
+ ->setKey('name')
+ ->setLabel(pht('Name'))
+ ->setDescription(pht('Name of the namespace.'))
+ ->setTransactionType(AlmanacNamespaceTransaction::TYPE_NAME)
+ ->setIsRequired(true)
+ ->setValue($object->getName()),
+ );
+ }
+
+}
diff --git a/src/applications/almanac/editor/AlmanacServiceEditor.php b/src/applications/almanac/editor/AlmanacNamespaceEditor.php
copy from src/applications/almanac/editor/AlmanacServiceEditor.php
copy to src/applications/almanac/editor/AlmanacNamespaceEditor.php
--- a/src/applications/almanac/editor/AlmanacServiceEditor.php
+++ b/src/applications/almanac/editor/AlmanacNamespaceEditor.php
@@ -1,6 +1,6 @@
<?php
-final class AlmanacServiceEditor
+final class AlmanacNamespaceEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
@@ -8,7 +8,7 @@
}
public function getEditorObjectsDescription() {
- return pht('Almanac Service');
+ return pht('Almanac Namespace');
}
protected function supportsSearch() {
@@ -18,9 +18,7 @@
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
- $types[] = AlmanacServiceTransaction::TYPE_NAME;
- $types[] = AlmanacServiceTransaction::TYPE_LOCK;
-
+ $types[] = AlmanacNamespaceTransaction::TYPE_NAME;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
@@ -31,10 +29,8 @@
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
- case AlmanacServiceTransaction::TYPE_NAME:
+ case AlmanacNamespaceTransaction::TYPE_NAME:
return $object->getName();
- case AlmanacServiceTransaction::TYPE_LOCK:
- return (bool)$object->getIsLocked();
}
return parent::getCustomTransactionOldValue($object, $xaction);
@@ -45,10 +41,8 @@
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
- case AlmanacServiceTransaction::TYPE_NAME:
+ case AlmanacNamespaceTransaction::TYPE_NAME:
return $xaction->getNewValue();
- case AlmanacServiceTransaction::TYPE_LOCK:
- return (bool)$xaction->getNewValue();
}
return parent::getCustomTransactionNewValue($object, $xaction);
@@ -59,12 +53,9 @@
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
- case AlmanacServiceTransaction::TYPE_NAME:
+ case AlmanacNamespaceTransaction::TYPE_NAME:
$object->setName($xaction->getNewValue());
return;
- case AlmanacServiceTransaction::TYPE_LOCK:
- $object->setIsLocked((int)$xaction->getNewValue());
- return;
}
return parent::applyCustomInternalTransaction($object, $xaction);
@@ -75,24 +66,7 @@
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
- case AlmanacServiceTransaction::TYPE_NAME:
- return;
- case AlmanacServiceTransaction::TYPE_LOCK:
- $service = id(new AlmanacServiceQuery())
- ->setViewer(PhabricatorUser::getOmnipotentUser())
- ->withPHIDs(array($object->getPHID()))
- ->needBindings(true)
- ->executeOne();
-
- $devices = array();
- foreach ($service->getBindings() as $binding) {
- $device = $binding->getInterface()->getDevice();
- $devices[$device->getPHID()] = $device;
- }
-
- foreach ($devices as $device) {
- $device->rebuildDeviceLocks();
- }
+ case AlmanacNamespaceTransaction::TYPE_NAME:
return;
}
@@ -107,7 +81,7 @@
$errors = parent::validateTransaction($object, $type, $xactions);
switch ($type) {
- case AlmanacServiceTransaction::TYPE_NAME:
+ case AlmanacNamespaceTransaction::TYPE_NAME:
$missing = $this->validateIsEmptyTextField(
$object->getName(),
$xactions);
@@ -116,19 +90,18 @@
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Required'),
- pht('Service name is required.'),
+ pht('Namespace name is required.'),
nonempty(last($xactions), null));
$error->setIsMissingFieldError(true);
$errors[] = $error;
} else {
foreach ($xactions as $xaction) {
- $message = null;
-
$name = $xaction->getNewValue();
+ $message = null;
try {
- AlmanacNames::validateServiceOrDeviceName($name);
+ AlmanacNames::validateName($name);
} catch (Exception $ex) {
$message = $ex->getMessage();
}
@@ -140,22 +113,25 @@
$message,
$xaction);
$errors[] = $error;
+ continue;
}
- }
- }
- if ($xactions) {
- $duplicate = id(new AlmanacServiceQuery())
- ->setViewer(PhabricatorUser::getOmnipotentUser())
- ->withNames(array(last($xactions)->getNewValue()))
- ->executeOne();
- if ($duplicate && ($duplicate->getID() != $object->getID())) {
- $error = new PhabricatorApplicationTransactionValidationError(
- $type,
- pht('Not Unique'),
- pht('Almanac services must have unique names.'),
- last($xactions));
- $errors[] = $error;
+ $other = id(new AlmanacNamespaceQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withNames(array($name))
+ ->executeOne();
+ if ($other && ($other->getID() != $object->getID())) {
+ $error = new PhabricatorApplicationTransactionValidationError(
+ $type,
+ pht('Invalid'),
+ pht(
+ 'The namespace name "%s" is already in use by another '.
+ 'namespace. Each namespace must have a unique name.',
+ $name),
+ $xaction);
+ $errors[] = $error;
+ continue;
+ }
}
}
@@ -165,6 +141,22 @@
return $errors;
}
+ protected function didCatchDuplicateKeyException(
+ PhabricatorLiskDAO $object,
+ array $xactions,
+ Exception $ex) {
+
+ $errors = array();
+ $errors[] = new PhabricatorApplicationTransactionValidationError(
+ null,
+ pht('Invalid'),
+ pht(
+ 'Another namespace with this name already exists. Each namespace '.
+ 'must have a unique name.'),
+ null);
+
+ throw new PhabricatorApplicationTransactionValidationException($errors);
+ }
}
diff --git a/src/applications/almanac/editor/AlmanacServiceEditor.php b/src/applications/almanac/editor/AlmanacServiceEditor.php
--- a/src/applications/almanac/editor/AlmanacServiceEditor.php
+++ b/src/applications/almanac/editor/AlmanacServiceEditor.php
@@ -128,7 +128,7 @@
$name = $xaction->getNewValue();
try {
- AlmanacNames::validateServiceOrDeviceName($name);
+ AlmanacNames::validateName($name);
} catch (Exception $ex) {
$message = $ex->getMessage();
}
diff --git a/src/applications/almanac/phid/AlmanacNamespacePHIDType.php b/src/applications/almanac/phid/AlmanacNamespacePHIDType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/almanac/phid/AlmanacNamespacePHIDType.php
@@ -0,0 +1,44 @@
+<?php
+
+final class AlmanacNamespacePHIDType extends PhabricatorPHIDType {
+
+ const TYPECONST = 'ANAM';
+
+ public function getTypeName() {
+ return pht('Almanac Namespace');
+ }
+
+ public function newObject() {
+ return new AlmanacNamespace();
+ }
+
+ public function getPHIDTypeApplicationClass() {
+ return 'PhabricatorAlmanacApplication';
+ }
+
+ protected function buildQueryForObjects(
+ PhabricatorObjectQuery $query,
+ array $phids) {
+
+ return id(new AlmanacNamespaceQuery())
+ ->withPHIDs($phids);
+ }
+
+ public function loadHandles(
+ PhabricatorHandleQuery $query,
+ array $handles,
+ array $objects) {
+
+ foreach ($handles as $phid => $handle) {
+ $namespace = $objects[$phid];
+
+ $id = $namespace->getID();
+ $name = $namespace->getName();
+
+ $handle->setObjectName(pht('Namespace %d', $id));
+ $handle->setName($name);
+ $handle->setURI($namespace->getURI());
+ }
+ }
+
+}
diff --git a/src/applications/almanac/query/AlmanacNamespaceQuery.php b/src/applications/almanac/query/AlmanacNamespaceQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/almanac/query/AlmanacNamespaceQuery.php
@@ -0,0 +1,103 @@
+<?php
+
+final class AlmanacNamespaceQuery
+ extends PhabricatorCursorPagedPolicyAwareQuery {
+
+ private $ids;
+ private $phids;
+ private $names;
+
+ public function withIDs(array $ids) {
+ $this->ids = $ids;
+ return $this;
+ }
+
+ public function withPHIDs(array $phids) {
+ $this->phids = $phids;
+ return $this;
+ }
+
+ public function withNames(array $names) {
+ $this->names = $names;
+ return $this;
+ }
+
+ public function withNameNgrams($ngrams) {
+ return $this->withNgramsConstraint(
+ new AlmanacNamespaceNameNgrams(),
+ $ngrams);
+ }
+
+ public function newResultObject() {
+ return new AlmanacNamespace();
+ }
+
+ protected function loadPage() {
+ return $this->loadStandardPage($this->newResultObject());
+ }
+
+ protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
+ $where = parent::buildWhereClauseParts($conn);
+
+ if ($this->ids !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'namespace.id IN (%Ld)',
+ $this->ids);
+ }
+
+ if ($this->phids !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'namespace.phid IN (%Ls)',
+ $this->phids);
+ }
+
+ if ($this->names !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'namespace.name IN (%Ls)',
+ $this->names);
+ }
+
+ return $where;
+ }
+
+ protected function getPrimaryTableAlias() {
+ return 'namespace';
+ }
+
+ public function getOrderableColumns() {
+ return parent::getOrderableColumns() + array(
+ 'name' => array(
+ 'table' => $this->getPrimaryTableAlias(),
+ 'column' => 'name',
+ 'type' => 'string',
+ 'unique' => true,
+ 'reverse' => true,
+ ),
+ );
+ }
+
+ protected function getPagingValueMap($cursor, array $keys) {
+ $namespace = $this->loadCursorObject($cursor);
+ return array(
+ 'id' => $namespace->getID(),
+ 'name' => $namespace->getName(),
+ );
+ }
+
+ public function getBuiltinOrders() {
+ return array(
+ 'name' => array(
+ 'vector' => array('name'),
+ 'name' => pht('Namespace Name'),
+ ),
+ ) + parent::getBuiltinOrders();
+ }
+
+ public function getQueryApplicationClass() {
+ return 'PhabricatorAlmanacApplication';
+ }
+
+}
diff --git a/src/applications/almanac/query/AlmanacNetworkSearchEngine.php b/src/applications/almanac/query/AlmanacNamespaceSearchEngine.php
copy from src/applications/almanac/query/AlmanacNetworkSearchEngine.php
copy to src/applications/almanac/query/AlmanacNamespaceSearchEngine.php
--- a/src/applications/almanac/query/AlmanacNetworkSearchEngine.php
+++ b/src/applications/almanac/query/AlmanacNamespaceSearchEngine.php
@@ -1,10 +1,10 @@
<?php
-final class AlmanacNetworkSearchEngine
+final class AlmanacNamespaceSearchEngine
extends PhabricatorApplicationSearchEngine {
public function getResultTypeDescription() {
- return pht('Almanac Networks');
+ return pht('Almanac Namespaces');
}
public function getApplicationClassName() {
@@ -12,7 +12,7 @@
}
public function newQuery() {
- return new AlmanacNetworkQuery();
+ return new AlmanacNamespaceQuery();
}
protected function buildCustomSearchFields() {
@@ -20,7 +20,7 @@
id(new PhabricatorSearchTextField())
->setLabel(pht('Name Contains'))
->setKey('match')
- ->setDescription(pht('Search for devices by name substring.')),
+ ->setDescription(pht('Search for namespaces by name substring.')),
);
}
@@ -35,12 +35,12 @@
}
protected function getURI($path) {
- return '/almanac/network/'.$path;
+ return '/almanac/namespace/'.$path;
}
protected function getBuiltinQueryNames() {
$names = array(
- 'all' => pht('All Networks'),
+ 'all' => pht('All Namespaces'),
);
return $names;
@@ -60,30 +60,30 @@
}
protected function renderResultList(
- array $networks,
+ array $namespaces,
PhabricatorSavedQuery $query,
array $handles) {
- assert_instances_of($networks, 'AlmanacNetwork');
+ assert_instances_of($namespaces, 'AlmanacNamespace');
$viewer = $this->requireViewer();
$list = new PHUIObjectItemListView();
$list->setUser($viewer);
- foreach ($networks as $network) {
- $id = $network->getID();
+ foreach ($namespaces as $namespace) {
+ $id = $namespace->getID();
$item = id(new PHUIObjectItemView())
- ->setObjectName(pht('Network %d', $id))
- ->setHeader($network->getName())
- ->setHref($this->getApplicationURI("network/{$id}/"))
- ->setObject($network);
+ ->setObjectName(pht('Namespace %d', $id))
+ ->setHeader($namespace->getName())
+ ->setHref($this->getApplicationURI("namespace/{$id}/"))
+ ->setObject($namespace);
$list->addItem($item);
}
$result = new PhabricatorApplicationSearchResultView();
$result->setObjectList($list);
- $result->setNoDataString(pht('No Almanac Networks found.'));
+ $result->setNoDataString(pht('No Almanac namespaces found.'));
return $result;
}
diff --git a/src/applications/almanac/query/AlmanacNamespaceTransactionQuery.php b/src/applications/almanac/query/AlmanacNamespaceTransactionQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/almanac/query/AlmanacNamespaceTransactionQuery.php
@@ -0,0 +1,10 @@
+<?php
+
+final class AlmanacNamespaceTransactionQuery
+ extends PhabricatorApplicationTransactionQuery {
+
+ public function getTemplateApplicationTransaction() {
+ return new AlmanacNamespaceTransaction();
+ }
+
+}
diff --git a/src/applications/almanac/query/AlmanacNetworkSearchEngine.php b/src/applications/almanac/query/AlmanacNetworkSearchEngine.php
--- a/src/applications/almanac/query/AlmanacNetworkSearchEngine.php
+++ b/src/applications/almanac/query/AlmanacNetworkSearchEngine.php
@@ -20,7 +20,7 @@
id(new PhabricatorSearchTextField())
->setLabel(pht('Name Contains'))
->setKey('match')
- ->setDescription(pht('Search for devices by name substring.')),
+ ->setDescription(pht('Search for networks by name substring.')),
);
}
diff --git a/src/applications/almanac/storage/AlmanacDevice.php b/src/applications/almanac/storage/AlmanacDevice.php
--- a/src/applications/almanac/storage/AlmanacDevice.php
+++ b/src/applications/almanac/storage/AlmanacDevice.php
@@ -56,7 +56,7 @@
}
public function save() {
- AlmanacNames::validateServiceOrDeviceName($this->getName());
+ AlmanacNames::validateName($this->getName());
$this->nameIndex = PhabricatorHash::digestForIndex($this->getName());
diff --git a/src/applications/almanac/storage/AlmanacService.php b/src/applications/almanac/storage/AlmanacNamespace.php
copy from src/applications/almanac/storage/AlmanacService.php
copy to src/applications/almanac/storage/AlmanacNamespace.php
--- a/src/applications/almanac/storage/AlmanacService.php
+++ b/src/applications/almanac/storage/AlmanacNamespace.php
@@ -1,6 +1,6 @@
<?php
-final class AlmanacService
+final class AlmanacNamespace
extends AlmanacDAO
implements
PhabricatorPolicyInterface,
@@ -16,20 +16,15 @@
protected $mailKey;
protected $viewPolicy;
protected $editPolicy;
- protected $serviceClass;
- protected $isLocked;
private $customFields = self::ATTACHABLE;
private $almanacProperties = self::ATTACHABLE;
- private $bindings = self::ATTACHABLE;
- private $serviceType = self::ATTACHABLE;
- public static function initializeNewService() {
- return id(new AlmanacService())
+ public static function initializeNewNamespace() {
+ return id(new self())
->setViewPolicy(PhabricatorPolicies::POLICY_USER)
->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN)
- ->attachAlmanacProperties(array())
- ->setIsLocked(0);
+ ->attachAlmanacProperties(array());
}
protected function getConfiguration() {
@@ -39,30 +34,26 @@
'name' => 'text128',
'nameIndex' => 'bytes12',
'mailKey' => 'bytes20',
- 'serviceClass' => 'text64',
- 'isLocked' => 'bool',
),
self::CONFIG_KEY_SCHEMA => array(
- 'key_name' => array(
+ 'key_nameindex' => array(
'columns' => array('nameIndex'),
'unique' => true,
),
- 'key_nametext' => array(
+ 'key_name' => array(
'columns' => array('name'),
),
- 'key_class' => array(
- 'columns' => array('serviceClass'),
- ),
),
) + parent::getConfiguration();
}
public function generatePHID() {
- return PhabricatorPHID::generateNewPHID(AlmanacServicePHIDType::TYPECONST);
+ return PhabricatorPHID::generateNewPHID(
+ AlmanacNamespacePHIDType::TYPECONST);
}
public function save() {
- AlmanacNames::validateServiceOrDeviceName($this->getName());
+ AlmanacNames::validateName($this->getName());
$this->nameIndex = PhabricatorHash::digestForIndex($this->getName());
@@ -74,25 +65,7 @@
}
public function getURI() {
- return '/almanac/service/view/'.$this->getName().'/';
- }
-
- public function getBindings() {
- return $this->assertAttached($this->bindings);
- }
-
- public function attachBindings(array $bindings) {
- $this->bindings = $bindings;
- return $this;
- }
-
- public function getServiceType() {
- return $this->assertAttached($this->serviceType);
- }
-
- public function attachServiceType(AlmanacServiceType $type) {
- $this->serviceType = $type;
- return $this;
+ return '/almanac/namespace/view/'.$this->getName().'/';
}
@@ -127,7 +100,7 @@
}
public function getAlmanacPropertyFieldSpecifications() {
- return $this->getServiceType()->getFieldSpecifications();
+ return array();
}
@@ -146,11 +119,7 @@
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
- if ($this->getIsLocked()) {
- return PhabricatorPolicies::POLICY_NOONE;
- } else {
- return $this->getEditPolicy();
- }
+ return $this->getEditPolicy();
}
}
@@ -159,14 +128,6 @@
}
public function describeAutomaticCapability($capability) {
- switch ($capability) {
- case PhabricatorPolicyCapability::CAN_EDIT:
- if ($this->getIsLocked()) {
- return pht('This service is locked and can not be edited.');
- }
- break;
- }
-
return null;
}
@@ -196,7 +157,7 @@
public function getApplicationTransactionEditor() {
- return new AlmanacServiceEditor();
+ return new AlmanacNamespaceEditor();
}
public function getApplicationTransactionObject() {
@@ -204,13 +165,12 @@
}
public function getApplicationTransactionTemplate() {
- return new AlmanacServiceTransaction();
+ return new AlmanacNamespaceTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
-
return $timeline;
}
@@ -220,15 +180,6 @@
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
-
- $bindings = id(new AlmanacBindingQuery())
- ->setViewer($engine->getViewer())
- ->withServicePHIDs(array($this->getPHID()))
- ->execute();
- foreach ($bindings as $binding) {
- $engine->destroyObject($binding);
- }
-
$this->delete();
}
@@ -238,7 +189,7 @@
public function newNgrams() {
return array(
- id(new AlmanacServiceNameNgrams())
+ id(new AlmanacNamespaceNameNgrams())
->setValue($this->getName()),
);
}
diff --git a/src/applications/almanac/storage/AlmanacNamespaceNameNgrams.php b/src/applications/almanac/storage/AlmanacNamespaceNameNgrams.php
new file mode 100644
--- /dev/null
+++ b/src/applications/almanac/storage/AlmanacNamespaceNameNgrams.php
@@ -0,0 +1,18 @@
+<?php
+
+final class AlmanacNamespaceNameNgrams
+ extends PhabricatorSearchNgrams {
+
+ public function getNgramKey() {
+ return 'namespacename';
+ }
+
+ public function getColumnName() {
+ return 'name';
+ }
+
+ public function getApplicationName() {
+ return 'almanac';
+ }
+
+}
diff --git a/src/applications/almanac/storage/AlmanacNamespaceTransaction.php b/src/applications/almanac/storage/AlmanacNamespaceTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/almanac/storage/AlmanacNamespaceTransaction.php
@@ -0,0 +1,43 @@
+<?php
+
+final class AlmanacNamespaceTransaction
+ extends PhabricatorApplicationTransaction {
+
+ const TYPE_NAME = 'almanac:namespace:name';
+
+ public function getApplicationName() {
+ return 'almanac';
+ }
+
+ public function getApplicationTransactionType() {
+ return AlmanacNamespacePHIDType::TYPECONST;
+ }
+
+ public function getApplicationTransactionCommentObject() {
+ return null;
+ }
+
+ public function getTitle() {
+ $author_phid = $this->getAuthorPHID();
+
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+
+ switch ($this->getTransactionType()) {
+ case PhabricatorTransactions::TYPE_CREATE:
+ return pht(
+ '%s created this namespace.',
+ $this->renderHandleLink($author_phid));
+ break;
+ case self::TYPE_NAME:
+ return pht(
+ '%s renamed this namespace from "%s" to "%s".',
+ $this->renderHandleLink($author_phid),
+ $old,
+ $new);
+ }
+
+ return parent::getTitle();
+ }
+
+}
diff --git a/src/applications/almanac/storage/AlmanacService.php b/src/applications/almanac/storage/AlmanacService.php
--- a/src/applications/almanac/storage/AlmanacService.php
+++ b/src/applications/almanac/storage/AlmanacService.php
@@ -62,7 +62,7 @@
}
public function save() {
- AlmanacNames::validateServiceOrDeviceName($this->getName());
+ AlmanacNames::validateName($this->getName());
$this->nameIndex = PhabricatorHash::digestForIndex($this->getName());
diff --git a/src/applications/almanac/util/AlmanacNames.php b/src/applications/almanac/util/AlmanacNames.php
--- a/src/applications/almanac/util/AlmanacNames.php
+++ b/src/applications/almanac/util/AlmanacNames.php
@@ -2,54 +2,61 @@
final class AlmanacNames extends Phobject {
- public static function validateServiceOrDeviceName($name) {
+ public static function validateName($name) {
if (strlen($name) < 3) {
throw new Exception(
pht(
- 'Almanac service and device names must be at least 3 '.
- 'characters long.'));
+ 'Almanac service, device, property and namespace names must be '.
+ 'at least 3 characters long.'));
+ }
+
+ if (strlen($name) > 100) {
+ throw new Exception(
+ pht(
+ 'Almanac service, device, property and namespace names may not '.
+ 'be more than 100 characters long.'));
}
if (!preg_match('/^[a-z0-9.-]+\z/', $name)) {
throw new Exception(
pht(
- 'Almanac service and device names may only contain lowercase '.
- 'letters, numbers, hyphens, and periods.'));
+ 'Almanac service, device, property and namespace names may only '.
+ 'contain lowercase letters, numbers, hyphens, and periods.'));
}
if (preg_match('/(^|\\.)\d+(\z|\\.)/', $name)) {
throw new Exception(
pht(
- 'Almanac service and device names may not have any segments '.
- 'containing only digits.'));
+ 'Almanac service, device, property and namespace names may not '.
+ 'have any segments containing only digits.'));
}
if (preg_match('/\.\./', $name)) {
throw new Exception(
pht(
- 'Almanac service and device names may not contain multiple '.
- 'consecutive periods.'));
+ 'Almanac service, device, property and namespace names may not '.
+ 'contain multiple consecutive periods.'));
}
if (preg_match('/\\.-|-\\./', $name)) {
throw new Exception(
pht(
- 'Amanac service and device names may not contain hyphens adjacent '.
- 'to periods.'));
+ 'Almanac service, device, property and namespace names may not '.
+ 'contain hyphens adjacent to periods.'));
}
if (preg_match('/--/', $name)) {
throw new Exception(
pht(
- 'Almanac service and device names may not contain multiple '.
- 'consecutive hyphens.'));
+ 'Almanac service, device, property and namespace names may not '.
+ 'contain multiple consecutive hyphens.'));
}
if (!preg_match('/^[a-z0-9].*[a-z0-9]\z/', $name)) {
throw new Exception(
pht(
- 'Almanac service and device names must begin and end with a letter '.
- 'or number.'));
+ 'Almanac service, device, property and namespace names must begin '.
+ 'and end with a letter or number.'));
}
}
diff --git a/src/applications/almanac/util/__tests__/AlmanacNamesTestCase.php b/src/applications/almanac/util/__tests__/AlmanacNamesTestCase.php
--- a/src/applications/almanac/util/__tests__/AlmanacNamesTestCase.php
+++ b/src/applications/almanac/util/__tests__/AlmanacNamesTestCase.php
@@ -33,12 +33,16 @@
'db.phacility.instance' => true,
'web002.useast.example.com' => true,
'master.example-corp.com' => true,
+
+ // Maximum length is 100.
+ str_repeat('a', 100) => true,
+ str_repeat('a', 101) => false,
);
foreach ($map as $input => $expect) {
$caught = null;
try {
- AlmanacNames::validateServiceOrDeviceName($input);
+ AlmanacNames::validateName($input);
} catch (Exception $ex) {
$caught = $ex;
}
diff --git a/src/docs/user/userguide/almanac.diviner b/src/docs/user/userguide/almanac.diviner
--- a/src/docs/user/userguide/almanac.diviner
+++ b/src/docs/user/userguide/almanac.diviner
@@ -1,13 +1,138 @@
@title Almanac User Guide
@group userguide
-Using Almanac to manage services.
+Using Almanac to manage devices and services.
-= Overview =
+Overview
+========
IMPORTANT: Almanac is a prototype application. See
@{article:User Guide: Prototype Applications}.
+Almanac is a device and service inventory application. It allows you to create
+lists of //devices// and //services// that humans and other applications can
+use to keep track of what is running where.
+
+At a very high level, Almanac can be thought of as a bit like a DNS server.
+Callers ask it for information about services, and it responds with details
+about which devices host those services. However, it can respond to a broader
+range of queries and provide more detailed responses than DNS alone can.
+
+Today, the primary use cases for Almanac involve configuring Phabricator
+itself: Almanac is used to configure Phabricator to operate in a cluster setup,
+and to expose hardware to Drydock so it can run build and integration tasks.
+
+Beyond internal uses, Almanac is a general-purpose service and device inventory
+application and can be used to configure and manage other types of service and
+hardware inventories, but these use cases are currently considered experimental
+and you should be exercise caution in pursuing them.
+
+
+Example: Drydock Build Pool
+================================
+
+Here's a quick example of how you might configure Almanac to solve a real-world
+problem. This section describes configuration at a high level to give you an
+introduction to Almanac concepts and a better idea of how the pieces fit
+together.
+
+In this scenario, we want to use Drydock to run some sort of build process. To
+do this, Drydock needs hardware to run on. We're going to use Almanac to tell
+Drydock about the hardware it should use.
+
+In this scenario, Almanac will work a bit like a DNS server. When we're done,
+Drydock will be able to query Almanac for information about a service (like
+`build.mycompany.com`) and get back information about which hosts are part of
+that service and where it should connect to.
+
+Before getting started, we need to create a **network**. For simplicity, let's
+suppose everything will be connected through the public internet. If you
+haven't already, you'd create a "Public Internet" network first.
+
+Once we have a network, we create the actual physical or virtual hosts by
+launching instances in EC2, or racking and powering on some servers, or already
+having some hardware on hand we want to use. We set the hosts up normally and
+connect them to the internet or network.
+
+After the hosts exist, we add them to Almanac as **devices**, like
+`build001.mycompany.com`, `build002.mycompany.com`, and so on. In Almanac,
+devices are usually physical or virtual hosts, although you could also use it
+to inventory other types of devices and hardware.
+
+For each **device**, we add an **interface**. This is just an address and port
+on a particular network. Since we're going to connect to these hosts over
+SSH, we'll add interfaces on the standard SSH port 22. An example configuration
+might look a little bit like this:
+
+| Device | Network | Address | Port |
+|--------|---------|---------|------|
+| `build001.mycompany.com` | Public Internet | 58.8.9.10 | 22
+| `build002.mycompany.com` | Public Internet | 58.8.9.11 | 22
+| ... | Public Internet | ... | 22
+
+Now, we create the **service**. This is what we'll tell Drydock about, and
+it can query for information about this service to find connected devices.
+Here, we'll call it `build.mycompany.com`.
+
+After creating the service, add **bindings** to the interfaces we configured
+above. This will tell Drydock where it should actually connect to.
+
+Once this is complete, we're done in Almanac and can continue configuration in
+Drydock, which is outside the scope of this example. Once everything is fully
+configured, this is how Almanac will be used by Drydock:
+
+ - Drydock will query information about `build.mycompany.com` from Almanac.
+ - Drydock will get back a list of bound interfaces, among other data.
+ - The interfaces provide information about addresses and ports that Drydock
+ can use to connect to the actual devices.
+
+You can now add and remove devices to the pool by binding them and unbinding
+them from the service.
+
+
+Concepts
+========
+
+The major concepts in Almanac are **devices*, **interfaces**, **services**,
+**bindings**, **networks**, and **namespaces**.
+
+**Devices**: Almanac devices represent physical or virtual devices.
+Usually, they are hosts (like `web001.mycompany.net`), although you could
+use devices to keep inventory of any other kind of device or physical asset
+(like phones, laptops, or office chairs).
+
+Each device has a name, and may have properties and interfaces.
+
+**Interfaces**: Interfaces are listening address/port combinations on devices.
+For example, if you have a webserver host device named `web001.mycompany.net`,
+you might add an interface on port `80`.
+
+Interfaces tell users and applications where they should connect to to access
+services and devices.
+
+**Services**: These are named services like `build.mycompany.net` that work
+a bit like DNS. Humans or other applications can look up a service to find
+configuration information and learn which devices are hosting the service.
+
+Each service has a name, and may have properties and bindings.
+
+**Bindings**: Bindings are connections between services and interfaces. They
+tell callers which devices host a named service.
+
+**Networks**: Networks allow Almanac to distingiush between addresses on
+different networks, like VPNs vs the public internet.
+
+If you have hosts in different VPNs or on private networks, you might have
+multiple devices which share the same IP address (like `10.0.0.3`). Networks
+allow Almanac to distinguish between devices with the same address on different
+sections of the network.
+
+**Namespaces**: Namespaces let you control who is permitted to create devices
+and services with particular names. For example, the namespace `mycompany.com`
+controls who can create services with names like `a.mycompany.com` and
+`b.mycompany.com`.
+
+
Locking and Unlocking Services
==============================
@@ -17,8 +142,8 @@
For more details on this scenario, see
@{article:User Guide: Phabricator Clusters}.
-Beyond hardening cluster definitions, you might also want to lock a service to
-prevent accidental edits.
+Beyond hardening cluster definitions, you might also want to lock a critical
+service to prevent accidental edits.
To lock a service, run:

File Metadata

Mime Type
text/plain
Expires
Fri, Oct 25, 9:51 AM (3 w, 2 d ago)
Storage Engine
amazon-s3
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
phabricator/secure/5b/2d/zmylgxttq6cfqvxp
Default Alt Text
D15324.diff (53 KB)

Event Timeline