mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-18 19:40:55 +01:00
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
This commit is contained in:
parent
50debecf52
commit
db50d0fb11
29 changed files with 1195 additions and 40 deletions
|
@ -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};
|
14
resources/sql/autopatches/20160221.almanac.8.namespace.sql
Normal file
14
resources/sql/autopatches/20160221.almanac.8.namespace.sql
Normal file
|
@ -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};
|
19
resources/sql/autopatches/20160221.almanac.9.namespacex.sql
Normal file
19
resources/sql/autopatches/20160221.almanac.9.namespacex.sql
Normal file
|
@ -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};
|
|
@ -28,6 +28,7 @@ phutil_register_library_map(array(
|
|||
'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 @@ phutil_register_library_map(array(
|
|||
'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 @@ phutil_register_library_map(array(
|
|||
),
|
||||
'AlmanacCreateClusterServicesCapability' => 'PhabricatorPolicyCapability',
|
||||
'AlmanacCreateDevicesCapability' => 'PhabricatorPolicyCapability',
|
||||
'AlmanacCreateNamespacesCapability' => 'PhabricatorPolicyCapability',
|
||||
'AlmanacCreateNetworksCapability' => 'PhabricatorPolicyCapability',
|
||||
'AlmanacCreateServicesCapability' => 'PhabricatorPolicyCapability',
|
||||
'AlmanacCustomField' => 'PhabricatorCustomField',
|
||||
|
@ -4047,6 +4062,28 @@ phutil_register_library_map(array(
|
|||
'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',
|
||||
|
|
|
@ -29,7 +29,7 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication {
|
|||
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 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication {
|
|||
'/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 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication {
|
|||
'(?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 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication {
|
|||
'edit/' => 'AlmanacPropertyEditController',
|
||||
'delete/' => 'AlmanacPropertyDeleteController',
|
||||
),
|
||||
'namespace/' => array(
|
||||
$this->getQueryRoutePattern() => 'AlmanacNamespaceListController',
|
||||
$this->getEditRoutePattern('edit/')
|
||||
=> 'AlmanacNamespaceEditController',
|
||||
'(?P<id>\d+)/' => 'AlmanacNamespaceViewController',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -84,6 +90,9 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication {
|
|||
AlmanacCreateNetworksCapability::CAPABILITY => array(
|
||||
'default' => PhabricatorPolicies::POLICY_ADMIN,
|
||||
),
|
||||
AlmanacCreateNamespacesCapability::CAPABILITY => array(
|
||||
'default' => PhabricatorPolicies::POLICY_ADMIN,
|
||||
),
|
||||
AlmanacCreateClusterServicesCapability::CAPABILITY => array(
|
||||
'default' => PhabricatorPolicies::POLICY_ADMIN,
|
||||
),
|
||||
|
|
|
@ -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.');
|
||||
}
|
||||
|
||||
}
|
|
@ -12,26 +12,52 @@ final class AlmanacConsoleController extends AlmanacController {
|
|||
$menu = id(new PHUIObjectItemListView())
|
||||
->setUser($viewer);
|
||||
|
||||
$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'));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacNamespaceEditController extends AlmanacController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
return id(new AlmanacNamespaceEditEngine())
|
||||
->setController($this)
|
||||
->buildResponse();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -55,7 +55,7 @@ final class AlmanacPropertyEditController
|
|||
} else {
|
||||
$caught = null;
|
||||
try {
|
||||
AlmanacNames::validateServiceOrDeviceName($name);
|
||||
AlmanacNames::validateName($name);
|
||||
} catch (Exception $ex) {
|
||||
$caught = $ex;
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ final class AlmanacPropertyEditController
|
|||
|
||||
// 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.
|
||||
|
|
|
@ -136,7 +136,7 @@ final class AlmanacDeviceEditor
|
|||
$name = $xaction->getNewValue();
|
||||
|
||||
try {
|
||||
AlmanacNames::validateServiceOrDeviceName($name);
|
||||
AlmanacNames::validateName($name);
|
||||
} catch (Exception $ex) {
|
||||
$message = $ex->getMessage();
|
||||
}
|
||||
|
|
|
@ -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()),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
162
src/applications/almanac/editor/AlmanacNamespaceEditor.php
Normal file
162
src/applications/almanac/editor/AlmanacNamespaceEditor.php
Normal file
|
@ -0,0 +1,162 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacNamespaceEditor
|
||||
extends PhabricatorApplicationTransactionEditor {
|
||||
|
||||
public function getEditorApplicationClass() {
|
||||
return 'PhabricatorAlmanacApplication';
|
||||
}
|
||||
|
||||
public function getEditorObjectsDescription() {
|
||||
return pht('Almanac Namespace');
|
||||
}
|
||||
|
||||
protected function supportsSearch() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getTransactionTypes() {
|
||||
$types = parent::getTransactionTypes();
|
||||
|
||||
$types[] = AlmanacNamespaceTransaction::TYPE_NAME;
|
||||
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
|
||||
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
protected function getCustomTransactionOldValue(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case AlmanacNamespaceTransaction::TYPE_NAME:
|
||||
return $object->getName();
|
||||
}
|
||||
|
||||
return parent::getCustomTransactionOldValue($object, $xaction);
|
||||
}
|
||||
|
||||
protected function getCustomTransactionNewValue(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case AlmanacNamespaceTransaction::TYPE_NAME:
|
||||
return $xaction->getNewValue();
|
||||
}
|
||||
|
||||
return parent::getCustomTransactionNewValue($object, $xaction);
|
||||
}
|
||||
|
||||
protected function applyCustomInternalTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case AlmanacNamespaceTransaction::TYPE_NAME:
|
||||
$object->setName($xaction->getNewValue());
|
||||
return;
|
||||
}
|
||||
|
||||
return parent::applyCustomInternalTransaction($object, $xaction);
|
||||
}
|
||||
|
||||
protected function applyCustomExternalTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case AlmanacNamespaceTransaction::TYPE_NAME:
|
||||
return;
|
||||
}
|
||||
|
||||
return parent::applyCustomExternalTransaction($object, $xaction);
|
||||
}
|
||||
|
||||
protected function validateTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
$type,
|
||||
array $xactions) {
|
||||
|
||||
$errors = parent::validateTransaction($object, $type, $xactions);
|
||||
|
||||
switch ($type) {
|
||||
case AlmanacNamespaceTransaction::TYPE_NAME:
|
||||
$missing = $this->validateIsEmptyTextField(
|
||||
$object->getName(),
|
||||
$xactions);
|
||||
|
||||
if ($missing) {
|
||||
$error = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Required'),
|
||||
pht('Namespace name is required.'),
|
||||
nonempty(last($xactions), null));
|
||||
|
||||
$error->setIsMissingFieldError(true);
|
||||
$errors[] = $error;
|
||||
} else {
|
||||
foreach ($xactions as $xaction) {
|
||||
$name = $xaction->getNewValue();
|
||||
|
||||
$message = null;
|
||||
try {
|
||||
AlmanacNames::validateName($name);
|
||||
} catch (Exception $ex) {
|
||||
$message = $ex->getMessage();
|
||||
}
|
||||
|
||||
if ($message !== null) {
|
||||
$error = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Invalid'),
|
||||
$message,
|
||||
$xaction);
|
||||
$errors[] = $error;
|
||||
continue;
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -128,7 +128,7 @@ final class AlmanacServiceEditor
|
|||
$name = $xaction->getNewValue();
|
||||
|
||||
try {
|
||||
AlmanacNames::validateServiceOrDeviceName($name);
|
||||
AlmanacNames::validateName($name);
|
||||
} catch (Exception $ex) {
|
||||
$message = $ex->getMessage();
|
||||
}
|
||||
|
|
44
src/applications/almanac/phid/AlmanacNamespacePHIDType.php
Normal file
44
src/applications/almanac/phid/AlmanacNamespacePHIDType.php
Normal file
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
103
src/applications/almanac/query/AlmanacNamespaceQuery.php
Normal file
103
src/applications/almanac/query/AlmanacNamespaceQuery.php
Normal file
|
@ -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';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacNamespaceSearchEngine
|
||||
extends PhabricatorApplicationSearchEngine {
|
||||
|
||||
public function getResultTypeDescription() {
|
||||
return pht('Almanac Namespaces');
|
||||
}
|
||||
|
||||
public function getApplicationClassName() {
|
||||
return 'PhabricatorAlmanacApplication';
|
||||
}
|
||||
|
||||
public function newQuery() {
|
||||
return new AlmanacNamespaceQuery();
|
||||
}
|
||||
|
||||
protected function buildCustomSearchFields() {
|
||||
return array(
|
||||
id(new PhabricatorSearchTextField())
|
||||
->setLabel(pht('Name Contains'))
|
||||
->setKey('match')
|
||||
->setDescription(pht('Search for namespaces by name substring.')),
|
||||
);
|
||||
}
|
||||
|
||||
protected function buildQueryFromParameters(array $map) {
|
||||
$query = $this->newQuery();
|
||||
|
||||
if ($map['match'] !== null) {
|
||||
$query->withNameNgrams($map['match']);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function getURI($path) {
|
||||
return '/almanac/namespace/'.$path;
|
||||
}
|
||||
|
||||
protected function getBuiltinQueryNames() {
|
||||
$names = array(
|
||||
'all' => pht('All Namespaces'),
|
||||
);
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
public function buildSavedQueryFromBuiltin($query_key) {
|
||||
|
||||
$query = $this->newSavedQuery();
|
||||
$query->setQueryKey($query_key);
|
||||
|
||||
switch ($query_key) {
|
||||
case 'all':
|
||||
return $query;
|
||||
}
|
||||
|
||||
return parent::buildSavedQueryFromBuiltin($query_key);
|
||||
}
|
||||
|
||||
protected function renderResultList(
|
||||
array $namespaces,
|
||||
PhabricatorSavedQuery $query,
|
||||
array $handles) {
|
||||
assert_instances_of($namespaces, 'AlmanacNamespace');
|
||||
|
||||
$viewer = $this->requireViewer();
|
||||
|
||||
$list = new PHUIObjectItemListView();
|
||||
$list->setUser($viewer);
|
||||
foreach ($namespaces as $namespace) {
|
||||
$id = $namespace->getID();
|
||||
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->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 namespaces found.'));
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacNamespaceTransactionQuery
|
||||
extends PhabricatorApplicationTransactionQuery {
|
||||
|
||||
public function getTemplateApplicationTransaction() {
|
||||
return new AlmanacNamespaceTransaction();
|
||||
}
|
||||
|
||||
}
|
|
@ -20,7 +20,7 @@ final class AlmanacNetworkSearchEngine
|
|||
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.')),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ final class AlmanacDevice
|
|||
}
|
||||
|
||||
public function save() {
|
||||
AlmanacNames::validateServiceOrDeviceName($this->getName());
|
||||
AlmanacNames::validateName($this->getName());
|
||||
|
||||
$this->nameIndex = PhabricatorHash::digestForIndex($this->getName());
|
||||
|
||||
|
|
197
src/applications/almanac/storage/AlmanacNamespace.php
Normal file
197
src/applications/almanac/storage/AlmanacNamespace.php
Normal file
|
@ -0,0 +1,197 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacNamespace
|
||||
extends AlmanacDAO
|
||||
implements
|
||||
PhabricatorPolicyInterface,
|
||||
PhabricatorCustomFieldInterface,
|
||||
PhabricatorApplicationTransactionInterface,
|
||||
PhabricatorProjectInterface,
|
||||
AlmanacPropertyInterface,
|
||||
PhabricatorDestructibleInterface,
|
||||
PhabricatorNgramsInterface {
|
||||
|
||||
protected $name;
|
||||
protected $nameIndex;
|
||||
protected $mailKey;
|
||||
protected $viewPolicy;
|
||||
protected $editPolicy;
|
||||
|
||||
private $customFields = self::ATTACHABLE;
|
||||
private $almanacProperties = self::ATTACHABLE;
|
||||
|
||||
public static function initializeNewNamespace() {
|
||||
return id(new self())
|
||||
->setViewPolicy(PhabricatorPolicies::POLICY_USER)
|
||||
->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN)
|
||||
->attachAlmanacProperties(array());
|
||||
}
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'name' => 'text128',
|
||||
'nameIndex' => 'bytes12',
|
||||
'mailKey' => 'bytes20',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_nameindex' => array(
|
||||
'columns' => array('nameIndex'),
|
||||
'unique' => true,
|
||||
),
|
||||
'key_name' => array(
|
||||
'columns' => array('name'),
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
public function generatePHID() {
|
||||
return PhabricatorPHID::generateNewPHID(
|
||||
AlmanacNamespacePHIDType::TYPECONST);
|
||||
}
|
||||
|
||||
public function save() {
|
||||
AlmanacNames::validateName($this->getName());
|
||||
|
||||
$this->nameIndex = PhabricatorHash::digestForIndex($this->getName());
|
||||
|
||||
if (!$this->mailKey) {
|
||||
$this->mailKey = Filesystem::readRandomCharacters(20);
|
||||
}
|
||||
|
||||
return parent::save();
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
return '/almanac/namespace/view/'.$this->getName().'/';
|
||||
}
|
||||
|
||||
|
||||
/* -( AlmanacPropertyInterface )------------------------------------------- */
|
||||
|
||||
|
||||
public function attachAlmanacProperties(array $properties) {
|
||||
assert_instances_of($properties, 'AlmanacProperty');
|
||||
$this->almanacProperties = mpull($properties, null, 'getFieldName');
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAlmanacProperties() {
|
||||
return $this->assertAttached($this->almanacProperties);
|
||||
}
|
||||
|
||||
public function hasAlmanacProperty($key) {
|
||||
$this->assertAttached($this->almanacProperties);
|
||||
return isset($this->almanacProperties[$key]);
|
||||
}
|
||||
|
||||
public function getAlmanacProperty($key) {
|
||||
return $this->assertAttachedKey($this->almanacProperties, $key);
|
||||
}
|
||||
|
||||
public function getAlmanacPropertyValue($key, $default = null) {
|
||||
if ($this->hasAlmanacProperty($key)) {
|
||||
return $this->getAlmanacProperty($key)->getFieldValue();
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
public function getAlmanacPropertyFieldSpecifications() {
|
||||
return array();
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
||||
public function getCapabilities() {
|
||||
return array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
);
|
||||
}
|
||||
|
||||
public function getPolicy($capability) {
|
||||
switch ($capability) {
|
||||
case PhabricatorPolicyCapability::CAN_VIEW:
|
||||
return $this->getViewPolicy();
|
||||
case PhabricatorPolicyCapability::CAN_EDIT:
|
||||
return $this->getEditPolicy();
|
||||
}
|
||||
}
|
||||
|
||||
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function describeAutomaticCapability($capability) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorCustomFieldInterface )------------------------------------ */
|
||||
|
||||
|
||||
public function getCustomFieldSpecificationForRole($role) {
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getCustomFieldBaseClass() {
|
||||
return 'AlmanacCustomField';
|
||||
}
|
||||
|
||||
public function getCustomFields() {
|
||||
return $this->assertAttached($this->customFields);
|
||||
}
|
||||
|
||||
public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
|
||||
$this->customFields = $fields;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
||||
|
||||
|
||||
public function getApplicationTransactionEditor() {
|
||||
return new AlmanacNamespaceEditor();
|
||||
}
|
||||
|
||||
public function getApplicationTransactionObject() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionTemplate() {
|
||||
return new AlmanacNamespaceTransaction();
|
||||
}
|
||||
|
||||
public function willRenderTimeline(
|
||||
PhabricatorApplicationTransactionView $timeline,
|
||||
AphrontRequest $request) {
|
||||
return $timeline;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorDestructibleInterface )----------------------------------- */
|
||||
|
||||
|
||||
public function destroyObjectPermanently(
|
||||
PhabricatorDestructionEngine $engine) {
|
||||
$this->delete();
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorNgramInterface )------------------------------------------ */
|
||||
|
||||
|
||||
public function newNgrams() {
|
||||
return array(
|
||||
id(new AlmanacNamespaceNameNgrams())
|
||||
->setValue($this->getName()),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -62,7 +62,7 @@ final class AlmanacService
|
|||
}
|
||||
|
||||
public function save() {
|
||||
AlmanacNames::validateServiceOrDeviceName($this->getName());
|
||||
AlmanacNames::validateName($this->getName());
|
||||
|
||||
$this->nameIndex = PhabricatorHash::digestForIndex($this->getName());
|
||||
|
||||
|
|
|
@ -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.'));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,12 +33,16 @@ final class AlmanacNamesTestCase extends PhabricatorTestCase {
|
|||
'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;
|
||||
}
|
||||
|
|
|
@ -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 @@ services prevents an attacker from modifying the Phabricator cluster definition.
|
|||
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:
|
||||
|
||||
|
|
Loading…
Reference in a new issue