mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-26 15:30:58 +01:00
Give AlmanacServices a service type
Summary: Ref T5833. This allows services to be typed, to distinguish between different kinds of services. This makes a few things easier: - It's easier for clients to select the services they're interested in (see note in T5873 about Phacility). This isn't a full-power solution, but gets is some of the way there. - It's easier to set appropriate permissions around when modifications to the Phabricator cluster are allowed. These service nodes need to be demarcated as special in some way no matter what (see T6741). This also defines a new policy for users who are permitted to create services. - It's easier to browse/review/understand services. - Future diffs will allow ServiceTypes to specify more service structure (for example, default properties) to make it easier to configure services correctly. Instead of a free-for-all, you'll get a useful list of things that consumers of the service expect to read. The "custom" service type allows unstructured/freeform services to be created. Test Plan: - Created a new service (and hit error cases). - Edited an existing service. - Saw service types on list and detail views. - Poked around new permission stuff. - Ran `almanac.queryservices` with service class specification. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T5833 Differential Revision: https://secure.phabricator.com/D10995
This commit is contained in:
parent
7d9bda59a6
commit
c85327ca3e
15 changed files with 315 additions and 3 deletions
|
@ -0,0 +1,8 @@
|
|||
ALTER TABLE {$NAMESPACE}_almanac.almanac_service
|
||||
ADD serviceClass VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT};
|
||||
|
||||
ALTER TABLE {$NAMESPACE}_almanac.almanac_service
|
||||
ADD KEY `key_class` (serviceClass);
|
||||
|
||||
UPDATE {$NAMESPACE}_almanac.almanac_service
|
||||
SET serviceClass = 'AlmanacCustomServiceType' WHERE serviceClass = '';
|
|
@ -19,14 +19,18 @@ phutil_register_library_map(array(
|
|||
'AlmanacBindingTransaction' => 'applications/almanac/storage/AlmanacBindingTransaction.php',
|
||||
'AlmanacBindingTransactionQuery' => 'applications/almanac/query/AlmanacBindingTransactionQuery.php',
|
||||
'AlmanacBindingViewController' => 'applications/almanac/controller/AlmanacBindingViewController.php',
|
||||
'AlmanacClusterRepositoryServiceType' => 'applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php',
|
||||
'AlmanacClusterServiceType' => 'applications/almanac/servicetype/AlmanacClusterServiceType.php',
|
||||
'AlmanacConduitAPIMethod' => 'applications/almanac/conduit/AlmanacConduitAPIMethod.php',
|
||||
'AlmanacConsoleController' => 'applications/almanac/controller/AlmanacConsoleController.php',
|
||||
'AlmanacController' => 'applications/almanac/controller/AlmanacController.php',
|
||||
'AlmanacCoreCustomField' => 'applications/almanac/customfield/AlmanacCoreCustomField.php',
|
||||
'AlmanacCreateClusterServicesCapability' => 'applications/almanac/capability/AlmanacCreateClusterServicesCapability.php',
|
||||
'AlmanacCreateDevicesCapability' => 'applications/almanac/capability/AlmanacCreateDevicesCapability.php',
|
||||
'AlmanacCreateNetworksCapability' => 'applications/almanac/capability/AlmanacCreateNetworksCapability.php',
|
||||
'AlmanacCreateServicesCapability' => 'applications/almanac/capability/AlmanacCreateServicesCapability.php',
|
||||
'AlmanacCustomField' => 'applications/almanac/customfield/AlmanacCustomField.php',
|
||||
'AlmanacCustomServiceType' => 'applications/almanac/servicetype/AlmanacCustomServiceType.php',
|
||||
'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.php',
|
||||
'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php',
|
||||
'AlmanacDeviceController' => 'applications/almanac/controller/AlmanacDeviceController.php',
|
||||
|
@ -79,6 +83,7 @@ phutil_register_library_map(array(
|
|||
'AlmanacServiceSearchEngine' => 'applications/almanac/query/AlmanacServiceSearchEngine.php',
|
||||
'AlmanacServiceTransaction' => 'applications/almanac/storage/AlmanacServiceTransaction.php',
|
||||
'AlmanacServiceTransactionQuery' => 'applications/almanac/query/AlmanacServiceTransactionQuery.php',
|
||||
'AlmanacServiceType' => 'applications/almanac/servicetype/AlmanacServiceType.php',
|
||||
'AlmanacServiceViewController' => 'applications/almanac/controller/AlmanacServiceViewController.php',
|
||||
'Aphront304Response' => 'aphront/response/Aphront304Response.php',
|
||||
'Aphront400Response' => 'aphront/response/Aphront400Response.php',
|
||||
|
@ -3018,6 +3023,8 @@ phutil_register_library_map(array(
|
|||
'AlmanacBindingTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'AlmanacBindingTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'AlmanacBindingViewController' => 'AlmanacServiceController',
|
||||
'AlmanacClusterRepositoryServiceType' => 'AlmanacClusterServiceType',
|
||||
'AlmanacClusterServiceType' => 'AlmanacServiceType',
|
||||
'AlmanacConduitAPIMethod' => 'ConduitAPIMethod',
|
||||
'AlmanacConsoleController' => 'AlmanacController',
|
||||
'AlmanacController' => 'PhabricatorController',
|
||||
|
@ -3025,10 +3032,12 @@ phutil_register_library_map(array(
|
|||
'AlmanacCustomField',
|
||||
'PhabricatorStandardCustomFieldInterface',
|
||||
),
|
||||
'AlmanacCreateClusterServicesCapability' => 'PhabricatorPolicyCapability',
|
||||
'AlmanacCreateDevicesCapability' => 'PhabricatorPolicyCapability',
|
||||
'AlmanacCreateNetworksCapability' => 'PhabricatorPolicyCapability',
|
||||
'AlmanacCreateServicesCapability' => 'PhabricatorPolicyCapability',
|
||||
'AlmanacCustomField' => 'PhabricatorCustomField',
|
||||
'AlmanacCustomServiceType' => 'AlmanacServiceType',
|
||||
'AlmanacDAO' => 'PhabricatorLiskDAO',
|
||||
'AlmanacDevice' => array(
|
||||
'AlmanacDAO',
|
||||
|
@ -3105,6 +3114,7 @@ phutil_register_library_map(array(
|
|||
'AlmanacServiceSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'AlmanacServiceTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'AlmanacServiceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'AlmanacServiceType' => 'Phobject',
|
||||
'AlmanacServiceViewController' => 'AlmanacServiceController',
|
||||
'Aphront304Response' => 'AphrontResponse',
|
||||
'Aphront400Response' => 'AphrontResponse',
|
||||
|
|
|
@ -74,6 +74,9 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication {
|
|||
AlmanacCreateNetworksCapability::CAPABILITY => array(
|
||||
'default' => PhabricatorPolicies::POLICY_ADMIN,
|
||||
),
|
||||
AlmanacCreateClusterServicesCapability::CAPABILITY => array(
|
||||
'default' => PhabricatorPolicies::POLICY_ADMIN,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacCreateClusterServicesCapability
|
||||
extends PhabricatorPolicyCapability {
|
||||
|
||||
const CAPABILITY = 'almanac.cluster';
|
||||
|
||||
public function getCapabilityName() {
|
||||
return pht('Can Create Cluster Services');
|
||||
}
|
||||
|
||||
public function describeCapabilityRejection() {
|
||||
return pht(
|
||||
'You do not have permission to create Almanac cluster services.');
|
||||
}
|
||||
|
||||
}
|
|
@ -16,6 +16,7 @@ final class AlmanacQueryServicesConduitAPIMethod
|
|||
'ids' => 'optional list<id>',
|
||||
'phids' => 'optional list<phid>',
|
||||
'names' => 'optional list<phid>',
|
||||
'serviceClasses' => 'optional list<string>',
|
||||
) + self::getPagerParamTypes();
|
||||
}
|
||||
|
||||
|
@ -49,6 +50,11 @@ final class AlmanacQueryServicesConduitAPIMethod
|
|||
$query->withNames($names);
|
||||
}
|
||||
|
||||
$classes = $request->getValue('serviceClasses');
|
||||
if ($classes !== null) {
|
||||
$query->withServiceClasses($classes);
|
||||
}
|
||||
|
||||
$pager = $this->newPager($request);
|
||||
|
||||
$services = $query->executeWithCursorPager($pager);
|
||||
|
@ -84,6 +90,7 @@ final class AlmanacQueryServicesConduitAPIMethod
|
|||
'phid' => $service->getPHID(),
|
||||
'name' => $service->getName(),
|
||||
'uri' => PhabricatorEnv::getProductionURI($service->getURI()),
|
||||
'serviceClass' => $service->getServiceClass(),
|
||||
'properties' => $this->getPropertiesDictionary($service),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -29,13 +29,28 @@ final class AlmanacServiceEditController
|
|||
$title = pht('Edit Service');
|
||||
$save_button = pht('Save Changes');
|
||||
} else {
|
||||
$cancel_uri = $list_uri;
|
||||
|
||||
$this->requireApplicationCapability(
|
||||
AlmanacCreateServicesCapability::CAPABILITY);
|
||||
|
||||
$service_class = $request->getStr('serviceClass');
|
||||
$service_types = AlmanacServiceType::getAllServiceTypes();
|
||||
if (empty($service_types[$service_class])) {
|
||||
return $this->buildServiceTypeResponse($service_types, $cancel_uri);
|
||||
}
|
||||
|
||||
$service_type = $service_types[$service_class];
|
||||
if ($service_type->isClusterServiceType()) {
|
||||
$this->requireApplicationCapability(
|
||||
AlmanacCreateClusterServicesCapability::CAPABILITY);
|
||||
}
|
||||
|
||||
$service = AlmanacService::initializeNewService();
|
||||
$service->setServiceClass($service_class);
|
||||
$service->attachServiceType($service_type);
|
||||
$is_new = true;
|
||||
|
||||
$cancel_uri = $list_uri;
|
||||
$title = pht('Create Service');
|
||||
$save_button = pht('Create Service');
|
||||
}
|
||||
|
@ -53,7 +68,7 @@ final class AlmanacServiceEditController
|
|||
$v_projects = array_reverse($v_projects);
|
||||
}
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
if ($request->isFormPost() && $request->getStr('edit')) {
|
||||
$v_name = $request->getStr('name');
|
||||
$v_view = $request->getStr('viewPolicy');
|
||||
$v_edit = $request->getStr('editPolicy');
|
||||
|
@ -115,6 +130,8 @@ final class AlmanacServiceEditController
|
|||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
->addHiddenInput('edit', true)
|
||||
->addHiddenInput('serviceClass', $service->getServiceClass())
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setLabel(pht('Name'))
|
||||
|
@ -167,4 +184,80 @@ final class AlmanacServiceEditController
|
|||
));
|
||||
}
|
||||
|
||||
private function buildServiceTypeResponse(array $service_types, $cancel_uri) {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$e_service = null;
|
||||
$errors = array();
|
||||
if ($request->isFormPost()) {
|
||||
$e_service = pht('Required');
|
||||
$errors[] = pht(
|
||||
'To create a new service, you must select a service type.');
|
||||
}
|
||||
|
||||
list($can_cluster, $cluster_link) = $this->explainApplicationCapability(
|
||||
AlmanacCreateClusterServicesCapability::CAPABILITY,
|
||||
pht('You have permission to create cluster services.'),
|
||||
pht('You do not have permission to create new cluster services.'));
|
||||
|
||||
|
||||
$type_control = id(new AphrontFormRadioButtonControl())
|
||||
->setLabel(pht('Service Type'))
|
||||
->setName('serviceClass')
|
||||
->setError($e_service);
|
||||
|
||||
foreach ($service_types as $service_type) {
|
||||
$is_cluster = $service_type->isClusterServiceType();
|
||||
$is_disabled = ($is_cluster && !$can_cluster);
|
||||
|
||||
if ($is_cluster) {
|
||||
$extra = $cluster_link;
|
||||
} else {
|
||||
$extra = null;
|
||||
}
|
||||
|
||||
$type_control->addButton(
|
||||
get_class($service_type),
|
||||
$service_type->getServiceTypeName(),
|
||||
array(
|
||||
$service_type->getServiceTypeDescription(),
|
||||
$extra,
|
||||
),
|
||||
$is_disabled ? 'disabled' : null,
|
||||
$is_disabled);
|
||||
}
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(pht('Create Service'));
|
||||
|
||||
$title = pht('Choose Service Type');
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
->appendChild($type_control)
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->setValue(pht('Continue'))
|
||||
->addCancelButton($cancel_uri));
|
||||
|
||||
$box = id(new PHUIObjectBoxView())
|
||||
->setFormErrors($errors)
|
||||
->setHeaderText($title)
|
||||
->appendChild($form);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
array(
|
||||
$crumbs,
|
||||
$box,
|
||||
),
|
||||
array(
|
||||
'title' => $title,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -65,6 +65,10 @@ final class AlmanacServiceViewController
|
|||
->setUser($viewer)
|
||||
->setObject($service);
|
||||
|
||||
$properties->addProperty(
|
||||
pht('Service Type'),
|
||||
$service->getServiceType()->getServiceTypeShortName());
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ final class AlmanacServiceQuery
|
|||
private $ids;
|
||||
private $phids;
|
||||
private $names;
|
||||
private $serviceClasses;
|
||||
private $needBindings;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
|
@ -23,6 +24,11 @@ final class AlmanacServiceQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withServiceClasses(array $classes) {
|
||||
$this->serviceClasses = $classes;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function needBindings($need_bindings) {
|
||||
$this->needBindings = $need_bindings;
|
||||
return $this;
|
||||
|
@ -72,11 +78,35 @@ final class AlmanacServiceQuery
|
|||
$hashes);
|
||||
}
|
||||
|
||||
if ($this->serviceClasses !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'serviceClass IN (%Ls)',
|
||||
$this->serviceClasses);
|
||||
}
|
||||
|
||||
$where[] = $this->buildPagingClause($conn_r);
|
||||
|
||||
return $this->formatWhereClause($where);
|
||||
}
|
||||
|
||||
protected function willFilterPage(array $services) {
|
||||
$service_types = AlmanacServiceType::getAllServiceTypes();
|
||||
|
||||
foreach ($services as $key => $service) {
|
||||
$service_class = $service->getServiceClass();
|
||||
$service_type = idx($service_types, $service_class);
|
||||
if (!$service_type) {
|
||||
$this->didRejectResult($service);
|
||||
unset($services[$key]);
|
||||
continue;
|
||||
}
|
||||
$service->attachServiceType($service_type);
|
||||
}
|
||||
|
||||
return $services;
|
||||
}
|
||||
|
||||
protected function didFilterPage(array $services) {
|
||||
if ($this->needBindings) {
|
||||
$service_phids = mpull($services, 'getPHID');
|
||||
|
|
|
@ -73,7 +73,10 @@ final class AlmanacServiceSearchEngine
|
|||
->setObjectName(pht('Service %d', $service->getID()))
|
||||
->setHeader($service->getName())
|
||||
->setHref($service->getURI())
|
||||
->setObject($service);
|
||||
->setObject($service)
|
||||
->addIcon(
|
||||
$service->getServiceType()->getServiceTypeIcon(),
|
||||
$service->getServiceType()->getServiceTypeShortName());
|
||||
|
||||
$list->addItem($item);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacClusterRepositoryServiceType
|
||||
extends AlmanacClusterServiceType {
|
||||
|
||||
public function getServiceTypeShortName() {
|
||||
return pht('Cluster Repository');
|
||||
}
|
||||
|
||||
public function getServiceTypeName() {
|
||||
return pht('Phabricator Cluster: Repository');
|
||||
}
|
||||
|
||||
public function getServiceTypeDescription() {
|
||||
return pht(
|
||||
'Defines a repository service for use in a Phabricator cluster.');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
abstract class AlmanacClusterServiceType
|
||||
extends AlmanacServiceType {
|
||||
|
||||
public function isClusterServiceType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getServiceTypeIcon() {
|
||||
return 'fa-sitemap';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacCustomServiceType extends AlmanacServiceType {
|
||||
|
||||
public function getServiceTypeShortName() {
|
||||
return pht('Custom');
|
||||
}
|
||||
|
||||
public function getServiceTypeName() {
|
||||
return pht('Custom Service');
|
||||
}
|
||||
|
||||
public function getServiceTypeDescription() {
|
||||
return pht('Defines a unstructured custom service.');
|
||||
}
|
||||
|
||||
}
|
64
src/applications/almanac/servicetype/AlmanacServiceType.php
Normal file
64
src/applications/almanac/servicetype/AlmanacServiceType.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
abstract class AlmanacServiceType extends Phobject {
|
||||
|
||||
|
||||
/**
|
||||
* Return a very short human-readable name for this service type, like
|
||||
* "Custom".
|
||||
*
|
||||
* @return string Very short human-readable service type name.
|
||||
*/
|
||||
abstract public function getServiceTypeShortName();
|
||||
|
||||
/**
|
||||
* Return a short, human-readable name for this service type, like
|
||||
* "Custom Service".
|
||||
*
|
||||
* @return string Human-readable name for this service type.
|
||||
*/
|
||||
abstract public function getServiceTypeName();
|
||||
|
||||
|
||||
/**
|
||||
* Return a brief summary of this service type.
|
||||
*
|
||||
* This summary should be a sentence or two long.
|
||||
*
|
||||
* @return string Brief, human-readable description of this service type.
|
||||
*/
|
||||
abstract public function getServiceTypeDescription();
|
||||
|
||||
|
||||
public function getServiceTypeIcon() {
|
||||
return 'fa-cog';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return `true` if this service type is a Phabricator cluster service type.
|
||||
*
|
||||
* These special services change the behavior of Phabricator, and require
|
||||
* elevated permission to create.
|
||||
*
|
||||
* @return bool True if this is a Phabricator cluster service type.
|
||||
*/
|
||||
public function isClusterServiceType() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* List all available service type implementations.
|
||||
*
|
||||
* @return map<string, object> Dictionary of available service types.
|
||||
*/
|
||||
public static function getAllServiceTypes() {
|
||||
$types = id(new PhutilSymbolLoader())
|
||||
->setAncestorClass(__CLASS__)
|
||||
->loadObjects();
|
||||
|
||||
return msort($types, 'getServiceTypeName');
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -14,10 +14,12 @@ final class AlmanacService
|
|||
protected $mailKey;
|
||||
protected $viewPolicy;
|
||||
protected $editPolicy;
|
||||
protected $serviceClass;
|
||||
|
||||
private $customFields = self::ATTACHABLE;
|
||||
private $almanacProperties = self::ATTACHABLE;
|
||||
private $bindings = self::ATTACHABLE;
|
||||
private $serviceType = self::ATTACHABLE;
|
||||
|
||||
public static function initializeNewService() {
|
||||
return id(new AlmanacService())
|
||||
|
@ -33,6 +35,7 @@ final class AlmanacService
|
|||
'name' => 'text128',
|
||||
'nameIndex' => 'bytes12',
|
||||
'mailKey' => 'bytes20',
|
||||
'serviceClass' => 'text64',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_name' => array(
|
||||
|
@ -42,6 +45,9 @@ final class AlmanacService
|
|||
'key_nametext' => array(
|
||||
'columns' => array('name'),
|
||||
),
|
||||
'key_class' => array(
|
||||
'columns' => array('serviceClass'),
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
@ -75,6 +81,15 @@ final class AlmanacService
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function getServiceType() {
|
||||
return $this->assertAttached($this->serviceType);
|
||||
}
|
||||
|
||||
public function attachServiceType(AlmanacServiceType $type) {
|
||||
$this->serviceType = $type;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/* -( AlmanacPropertyInterface )------------------------------------------- */
|
||||
|
||||
|
|
|
@ -81,6 +81,14 @@ abstract class DiffusionQuery extends PhabricatorQuery {
|
|||
'be loaded.'));
|
||||
}
|
||||
|
||||
$service_type = $service->getServiceType();
|
||||
if (!($service_type instanceof AlmanacClusterRepositoryServiceType)) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'The Alamnac service for this repository does not have the correct '.
|
||||
'service type.'));
|
||||
}
|
||||
|
||||
$bindings = $service->getBindings();
|
||||
if (!$bindings) {
|
||||
throw new Exception(
|
||||
|
|
Loading…
Reference in a new issue