1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-09-20 01:08:50 +02: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:
epriestley 2014-12-17 11:10:27 -08:00
parent 7d9bda59a6
commit c85327ca3e
15 changed files with 315 additions and 3 deletions

View file

@ -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 = '';

View file

@ -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',

View file

@ -74,6 +74,9 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication {
AlmanacCreateNetworksCapability::CAPABILITY => array(
'default' => PhabricatorPolicies::POLICY_ADMIN,
),
AlmanacCreateClusterServicesCapability::CAPABILITY => array(
'default' => PhabricatorPolicies::POLICY_ADMIN,
),
);
}

View file

@ -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.');
}
}

View file

@ -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),
);
}

View file

@ -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,
));
}
}

View file

@ -65,6 +65,10 @@ final class AlmanacServiceViewController
->setUser($viewer)
->setObject($service);
$properties->addProperty(
pht('Service Type'),
$service->getServiceType()->getServiceTypeShortName());
return $properties;
}

View file

@ -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');

View file

@ -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);
}

View file

@ -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.');
}
}

View file

@ -0,0 +1,14 @@
<?php
abstract class AlmanacClusterServiceType
extends AlmanacServiceType {
public function isClusterServiceType() {
return true;
}
public function getServiceTypeIcon() {
return 'fa-sitemap';
}
}

View file

@ -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.');
}
}

View 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');
}
}

View file

@ -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 )------------------------------------------- */

View file

@ -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(