mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-06 04:41:01 +01:00
(stable) Promote 2015 Week 42
This commit is contained in:
commit
064d9a9bfd
111 changed files with 3533 additions and 329 deletions
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
return array(
|
||||
'names' => array(
|
||||
'core.pkg.css' => 'a11c3643',
|
||||
'core.pkg.css' => 'c65b251d',
|
||||
'core.pkg.js' => '47dc9ebb',
|
||||
'darkconsole.pkg.js' => 'e7393ebb',
|
||||
'differential.pkg.css' => '2de124c9',
|
||||
|
@ -67,7 +67,7 @@ return array(
|
|||
'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55',
|
||||
'rsrc/css/application/diffusion/diffusion-icons.css' => '2941baf1',
|
||||
'rsrc/css/application/diffusion/diffusion-readme.css' => '2106ea08',
|
||||
'rsrc/css/application/diffusion/diffusion-source.css' => '66fdf661',
|
||||
'rsrc/css/application/diffusion/diffusion-source.css' => '075ba788',
|
||||
'rsrc/css/application/feed/feed.css' => 'ecd4ec57',
|
||||
'rsrc/css/application/files/global-drag-and-drop.css' => '697324ad',
|
||||
'rsrc/css/application/flag/flag.css' => '5337623f',
|
||||
|
@ -147,7 +147,7 @@ return array(
|
|||
'rsrc/css/phui/phui-status.css' => '888cedb8',
|
||||
'rsrc/css/phui/phui-tag-view.css' => '402691cc',
|
||||
'rsrc/css/phui/phui-text.css' => 'cf019f54',
|
||||
'rsrc/css/phui/phui-timeline-view.css' => 'f1bccf73',
|
||||
'rsrc/css/phui/phui-timeline-view.css' => '2efceff8',
|
||||
'rsrc/css/phui/phui-two-column-view.css' => '39ecafb1',
|
||||
'rsrc/css/phui/phui-workboard-view.css' => '6704d68d',
|
||||
'rsrc/css/phui/phui-workpanel-view.css' => 'adec7699',
|
||||
|
@ -521,7 +521,7 @@ return array(
|
|||
'differential-table-of-contents-css' => 'ae4b7a55',
|
||||
'diffusion-icons-css' => '2941baf1',
|
||||
'diffusion-readme-css' => '2106ea08',
|
||||
'diffusion-source-css' => '66fdf661',
|
||||
'diffusion-source-css' => '075ba788',
|
||||
'diviner-shared-css' => '5a337049',
|
||||
'font-fontawesome' => 'd2fc4e8d',
|
||||
'font-lato' => '5ab1a46a',
|
||||
|
@ -797,7 +797,7 @@ return array(
|
|||
'phui-tag-view-css' => '402691cc',
|
||||
'phui-text-css' => 'cf019f54',
|
||||
'phui-theme-css' => '6b451f24',
|
||||
'phui-timeline-view-css' => 'f1bccf73',
|
||||
'phui-timeline-view-css' => '2efceff8',
|
||||
'phui-two-column-view-css' => '39ecafb1',
|
||||
'phui-workboard-view-css' => '6704d68d',
|
||||
'phui-workpanel-view-css' => 'adec7699',
|
||||
|
|
|
@ -24,12 +24,14 @@ foreach (new LiskMigrationIterator($table) as $doc) {
|
|||
$prefix = 'projects/';
|
||||
if (($slug != $prefix) && (strncmp($slug, $prefix, strlen($prefix)) === 0)) {
|
||||
$parts = explode('/', $slug);
|
||||
$project_slug = $parts[1].'/';
|
||||
|
||||
$project_slug = $parts[1];
|
||||
$project_slug = PhabricatorSlug::normalizeProjectSlug($project_slug);
|
||||
|
||||
$project_slugs = array($project_slug);
|
||||
$project = id(new PhabricatorProjectQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withPhrictionSlugs($project_slugs)
|
||||
->withSlugs($project_slugs)
|
||||
->executeOne();
|
||||
|
||||
if ($project) {
|
||||
|
|
14
resources/sql/autopatches/20151009.drydock.auth.1.sql
Normal file
14
resources/sql/autopatches/20151009.drydock.auth.1.sql
Normal file
|
@ -0,0 +1,14 @@
|
|||
CREATE TABLE {$NAMESPACE}_drydock.drydock_authorization (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
phid VARBINARY(64) NOT NULL,
|
||||
blueprintPHID VARBINARY(64) NOT NULL,
|
||||
blueprintAuthorizationState VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
objectPHID VARBINARY(64) NOT NULL,
|
||||
objectAuthorizationState VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
dateModified INT UNSIGNED NOT NULL,
|
||||
UNIQUE KEY `key_phid` (phid),
|
||||
UNIQUE KEY `key_unique` (objectPHID, blueprintPHID),
|
||||
KEY `key_blueprint` (blueprintPHID, blueprintAuthorizationState),
|
||||
KEY `key_object` (objectPHID, objectAuthorizationState)
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
2
resources/sql/autopatches/20151010.drydock.auth.2.sql
Normal file
2
resources/sql/autopatches/20151010.drydock.auth.2.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_drydock.drydock_lease
|
||||
ADD authorizingPHID VARBINARY(64) NOT NULL;
|
16
resources/sql/autopatches/20151013.drydock.op.1.sql
Normal file
16
resources/sql/autopatches/20151013.drydock.op.1.sql
Normal file
|
@ -0,0 +1,16 @@
|
|||
CREATE TABLE {$NAMESPACE}_drydock.drydock_repositoryoperation (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
phid VARBINARY(64) NOT NULL,
|
||||
authorPHID VARBINARY(64) NOT NULL,
|
||||
objectPHID VARBINARY(64) NOT NULL,
|
||||
repositoryPHID VARBINARY(64) NOT NULL,
|
||||
repositoryTarget LONGBLOB NOT NULL,
|
||||
operationType VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
operationState VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
dateModified INT UNSIGNED NOT NULL,
|
||||
UNIQUE KEY `key_phid` (phid),
|
||||
KEY `key_object` (objectPHID),
|
||||
KEY `key_repository` (repositoryPHID, operationState)
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
|
@ -10,13 +10,14 @@ $projects = $table->loadAll();
|
|||
$slug_map = array();
|
||||
|
||||
foreach ($projects as $project) {
|
||||
$project->setPhrictionSlug($project->getName());
|
||||
$slug = $project->getPhrictionSlug();
|
||||
if ($slug == '/') {
|
||||
$slug = PhabricatorSlug::normalizeProjectSlug($project->getName());
|
||||
|
||||
if (!strlen($slug)) {
|
||||
$project_id = $project->getID();
|
||||
echo pht("Project #%d doesn't have a meaningful name...", $project_id)."\n";
|
||||
$project->setName(trim(pht('Unnamed Project %s', $project->getName())));
|
||||
}
|
||||
|
||||
$slug_map[$slug][] = $project->getID();
|
||||
}
|
||||
|
||||
|
@ -47,8 +48,8 @@ while ($update) {
|
|||
foreach ($update as $key => $project) {
|
||||
$id = $project->getID();
|
||||
$name = $project->getName();
|
||||
$project->setPhrictionSlug($name);
|
||||
$slug = $project->getPhrictionSlug();
|
||||
|
||||
$slug = PhabricatorSlug::normalizeProjectSlug($name).'/';
|
||||
|
||||
echo pht("Updating project #%d '%s' (%s)... ", $id, $name, $slug);
|
||||
try {
|
||||
|
@ -87,8 +88,8 @@ function rename_project($project, $projects) {
|
|||
$suffix = 2;
|
||||
while (true) {
|
||||
$new_name = $project->getName().' ('.$suffix.')';
|
||||
$project->setPhrictionSlug($new_name);
|
||||
$new_slug = $project->getPhrictionSlug();
|
||||
|
||||
$new_slug = PhabricatorSlug::normalizeProjectSlug($new_name).'/';
|
||||
|
||||
$okay = true;
|
||||
foreach ($projects as $other) {
|
||||
|
|
|
@ -467,6 +467,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialRevisionListController' => 'applications/differential/controller/DifferentialRevisionListController.php',
|
||||
'DifferentialRevisionListView' => 'applications/differential/view/DifferentialRevisionListView.php',
|
||||
'DifferentialRevisionMailReceiver' => 'applications/differential/mail/DifferentialRevisionMailReceiver.php',
|
||||
'DifferentialRevisionOperationController' => 'applications/differential/controller/DifferentialRevisionOperationController.php',
|
||||
'DifferentialRevisionPHIDType' => 'applications/differential/phid/DifferentialRevisionPHIDType.php',
|
||||
'DifferentialRevisionPackageHeraldField' => 'applications/differential/herald/DifferentialRevisionPackageHeraldField.php',
|
||||
'DifferentialRevisionPackageOwnerHeraldField' => 'applications/differential/herald/DifferentialRevisionPackageOwnerHeraldField.php',
|
||||
|
@ -607,6 +608,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionLowLevelGitRefQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelGitRefQuery.php',
|
||||
'DiffusionLowLevelMercurialBranchesQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelMercurialBranchesQuery.php',
|
||||
'DiffusionLowLevelMercurialPathsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelMercurialPathsQuery.php',
|
||||
'DiffusionLowLevelMercurialPathsQueryTests' => 'applications/diffusion/query/lowlevel/__tests__/DiffusionLowLevelMercurialPathsQueryTests.php',
|
||||
'DiffusionLowLevelParentsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelParentsQuery.php',
|
||||
'DiffusionLowLevelQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelQuery.php',
|
||||
'DiffusionLowLevelResolveRefsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php',
|
||||
|
@ -618,6 +620,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionMercurialServeSSHWorkflow' => 'applications/diffusion/ssh/DiffusionMercurialServeSSHWorkflow.php',
|
||||
'DiffusionMercurialWireClientSSHProtocolChannel' => 'applications/diffusion/ssh/DiffusionMercurialWireClientSSHProtocolChannel.php',
|
||||
'DiffusionMercurialWireProtocol' => 'applications/diffusion/protocol/DiffusionMercurialWireProtocol.php',
|
||||
'DiffusionMercurialWireProtocolTests' => 'applications/diffusion/protocol/__tests__/DiffusionMercurialWireProtocolTests.php',
|
||||
'DiffusionMercurialWireSSHTestCase' => 'applications/diffusion/ssh/__tests__/DiffusionMercurialWireSSHTestCase.php',
|
||||
'DiffusionMergedCommitsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionMergedCommitsQueryConduitAPIMethod.php',
|
||||
'DiffusionMirrorDeleteController' => 'applications/diffusion/controller/DiffusionMirrorDeleteController.php',
|
||||
|
@ -686,6 +689,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionRepositoryDefaultController' => 'applications/diffusion/controller/DiffusionRepositoryDefaultController.php',
|
||||
'DiffusionRepositoryEditActionsController' => 'applications/diffusion/controller/DiffusionRepositoryEditActionsController.php',
|
||||
'DiffusionRepositoryEditActivateController' => 'applications/diffusion/controller/DiffusionRepositoryEditActivateController.php',
|
||||
'DiffusionRepositoryEditAutomationController' => 'applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php',
|
||||
'DiffusionRepositoryEditBasicController' => 'applications/diffusion/controller/DiffusionRepositoryEditBasicController.php',
|
||||
'DiffusionRepositoryEditBranchesController' => 'applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php',
|
||||
'DiffusionRepositoryEditController' => 'applications/diffusion/controller/DiffusionRepositoryEditController.php',
|
||||
|
@ -798,6 +802,14 @@ phutil_register_library_map(array(
|
|||
'DoorkeeperTagsController' => 'applications/doorkeeper/controller/DoorkeeperTagsController.php',
|
||||
'DrydockAlmanacServiceHostBlueprintImplementation' => 'applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php',
|
||||
'DrydockApacheWebrootInterface' => 'applications/drydock/interface/webroot/DrydockApacheWebrootInterface.php',
|
||||
'DrydockAuthorization' => 'applications/drydock/storage/DrydockAuthorization.php',
|
||||
'DrydockAuthorizationAuthorizeController' => 'applications/drydock/controller/DrydockAuthorizationAuthorizeController.php',
|
||||
'DrydockAuthorizationListController' => 'applications/drydock/controller/DrydockAuthorizationListController.php',
|
||||
'DrydockAuthorizationListView' => 'applications/drydock/view/DrydockAuthorizationListView.php',
|
||||
'DrydockAuthorizationPHIDType' => 'applications/drydock/phid/DrydockAuthorizationPHIDType.php',
|
||||
'DrydockAuthorizationQuery' => 'applications/drydock/query/DrydockAuthorizationQuery.php',
|
||||
'DrydockAuthorizationSearchEngine' => 'applications/drydock/query/DrydockAuthorizationSearchEngine.php',
|
||||
'DrydockAuthorizationViewController' => 'applications/drydock/controller/DrydockAuthorizationViewController.php',
|
||||
'DrydockBlueprint' => 'applications/drydock/storage/DrydockBlueprint.php',
|
||||
'DrydockBlueprintController' => 'applications/drydock/controller/DrydockBlueprintController.php',
|
||||
'DrydockBlueprintCoreCustomField' => 'applications/drydock/customfield/DrydockBlueprintCoreCustomField.php',
|
||||
|
@ -828,6 +840,7 @@ phutil_register_library_map(array(
|
|||
'DrydockDefaultViewCapability' => 'applications/drydock/capability/DrydockDefaultViewCapability.php',
|
||||
'DrydockFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockFilesystemInterface.php',
|
||||
'DrydockInterface' => 'applications/drydock/interface/DrydockInterface.php',
|
||||
'DrydockLandRepositoryOperation' => 'applications/drydock/operation/DrydockLandRepositoryOperation.php',
|
||||
'DrydockLease' => 'applications/drydock/storage/DrydockLease.php',
|
||||
'DrydockLeaseAcquiredLogType' => 'applications/drydock/logtype/DrydockLeaseAcquiredLogType.php',
|
||||
'DrydockLeaseActivatedLogType' => 'applications/drydock/logtype/DrydockLeaseActivatedLogType.php',
|
||||
|
@ -838,6 +851,8 @@ phutil_register_library_map(array(
|
|||
'DrydockLeaseDestroyedLogType' => 'applications/drydock/logtype/DrydockLeaseDestroyedLogType.php',
|
||||
'DrydockLeaseListController' => 'applications/drydock/controller/DrydockLeaseListController.php',
|
||||
'DrydockLeaseListView' => 'applications/drydock/view/DrydockLeaseListView.php',
|
||||
'DrydockLeaseNoAuthorizationsLogType' => 'applications/drydock/logtype/DrydockLeaseNoAuthorizationsLogType.php',
|
||||
'DrydockLeaseNoBlueprintsLogType' => 'applications/drydock/logtype/DrydockLeaseNoBlueprintsLogType.php',
|
||||
'DrydockLeasePHIDType' => 'applications/drydock/phid/DrydockLeasePHIDType.php',
|
||||
'DrydockLeaseQuery' => 'applications/drydock/query/DrydockLeaseQuery.php',
|
||||
'DrydockLeaseQueuedLogType' => 'applications/drydock/logtype/DrydockLeaseQueuedLogType.php',
|
||||
|
@ -863,7 +878,16 @@ phutil_register_library_map(array(
|
|||
'DrydockManagementUpdateLeaseWorkflow' => 'applications/drydock/management/DrydockManagementUpdateLeaseWorkflow.php',
|
||||
'DrydockManagementUpdateResourceWorkflow' => 'applications/drydock/management/DrydockManagementUpdateResourceWorkflow.php',
|
||||
'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php',
|
||||
'DrydockObjectAuthorizationView' => 'applications/drydock/view/DrydockObjectAuthorizationView.php',
|
||||
'DrydockQuery' => 'applications/drydock/query/DrydockQuery.php',
|
||||
'DrydockRepositoryOperation' => 'applications/drydock/storage/DrydockRepositoryOperation.php',
|
||||
'DrydockRepositoryOperationListController' => 'applications/drydock/controller/DrydockRepositoryOperationListController.php',
|
||||
'DrydockRepositoryOperationPHIDType' => 'applications/drydock/phid/DrydockRepositoryOperationPHIDType.php',
|
||||
'DrydockRepositoryOperationQuery' => 'applications/drydock/query/DrydockRepositoryOperationQuery.php',
|
||||
'DrydockRepositoryOperationSearchEngine' => 'applications/drydock/query/DrydockRepositoryOperationSearchEngine.php',
|
||||
'DrydockRepositoryOperationType' => 'applications/drydock/operation/DrydockRepositoryOperationType.php',
|
||||
'DrydockRepositoryOperationUpdateWorker' => 'applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php',
|
||||
'DrydockRepositoryOperationViewController' => 'applications/drydock/controller/DrydockRepositoryOperationViewController.php',
|
||||
'DrydockResource' => 'applications/drydock/storage/DrydockResource.php',
|
||||
'DrydockResourceActivationFailureLogType' => 'applications/drydock/logtype/DrydockResourceActivationFailureLogType.php',
|
||||
'DrydockResourceActivationYieldLogType' => 'applications/drydock/logtype/DrydockResourceActivationYieldLogType.php',
|
||||
|
@ -1437,6 +1461,7 @@ phutil_register_library_map(array(
|
|||
'PHUIPropertyListExample' => 'applications/uiexample/examples/PHUIPropertyListExample.php',
|
||||
'PHUIPropertyListView' => 'view/phui/PHUIPropertyListView.php',
|
||||
'PHUIRemarkupPreviewPanel' => 'view/phui/PHUIRemarkupPreviewPanel.php',
|
||||
'PHUIRemarkupView' => 'infrastructure/markup/view/PHUIRemarkupView.php',
|
||||
'PHUISpacesNamespaceContextView' => 'applications/spaces/view/PHUISpacesNamespaceContextView.php',
|
||||
'PHUIStatusItemView' => 'view/phui/PHUIStatusItemView.php',
|
||||
'PHUIStatusListView' => 'view/phui/PHUIStatusListView.php',
|
||||
|
@ -2945,6 +2970,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSpacesTestCase' => 'applications/spaces/__tests__/PhabricatorSpacesTestCase.php',
|
||||
'PhabricatorSpacesViewController' => 'applications/spaces/controller/PhabricatorSpacesViewController.php',
|
||||
'PhabricatorStandardCustomField' => 'infrastructure/customfield/standard/PhabricatorStandardCustomField.php',
|
||||
'PhabricatorStandardCustomFieldBlueprints' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldBlueprints.php',
|
||||
'PhabricatorStandardCustomFieldBool' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldBool.php',
|
||||
'PhabricatorStandardCustomFieldCredential' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldCredential.php',
|
||||
'PhabricatorStandardCustomFieldDatasource' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldDatasource.php',
|
||||
|
@ -4190,6 +4216,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialRevisionListController' => 'DifferentialController',
|
||||
'DifferentialRevisionListView' => 'AphrontView',
|
||||
'DifferentialRevisionMailReceiver' => 'PhabricatorObjectMailReceiver',
|
||||
'DifferentialRevisionOperationController' => 'DifferentialController',
|
||||
'DifferentialRevisionPHIDType' => 'PhabricatorPHIDType',
|
||||
'DifferentialRevisionPackageHeraldField' => 'DifferentialRevisionHeraldField',
|
||||
'DifferentialRevisionPackageOwnerHeraldField' => 'DifferentialRevisionHeraldField',
|
||||
|
@ -4330,6 +4357,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionLowLevelGitRefQuery' => 'DiffusionLowLevelQuery',
|
||||
'DiffusionLowLevelMercurialBranchesQuery' => 'DiffusionLowLevelQuery',
|
||||
'DiffusionLowLevelMercurialPathsQuery' => 'DiffusionLowLevelQuery',
|
||||
'DiffusionLowLevelMercurialPathsQueryTests' => 'PhabricatorTestCase',
|
||||
'DiffusionLowLevelParentsQuery' => 'DiffusionLowLevelQuery',
|
||||
'DiffusionLowLevelQuery' => 'Phobject',
|
||||
'DiffusionLowLevelResolveRefsQuery' => 'DiffusionLowLevelQuery',
|
||||
|
@ -4341,6 +4369,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionMercurialServeSSHWorkflow' => 'DiffusionMercurialSSHWorkflow',
|
||||
'DiffusionMercurialWireClientSSHProtocolChannel' => 'PhutilProtocolChannel',
|
||||
'DiffusionMercurialWireProtocol' => 'Phobject',
|
||||
'DiffusionMercurialWireProtocolTests' => 'PhabricatorTestCase',
|
||||
'DiffusionMercurialWireSSHTestCase' => 'PhabricatorTestCase',
|
||||
'DiffusionMergedCommitsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
|
||||
'DiffusionMirrorDeleteController' => 'DiffusionController',
|
||||
|
@ -4409,6 +4438,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionRepositoryDefaultController' => 'DiffusionController',
|
||||
'DiffusionRepositoryEditActionsController' => 'DiffusionRepositoryEditController',
|
||||
'DiffusionRepositoryEditActivateController' => 'DiffusionRepositoryEditController',
|
||||
'DiffusionRepositoryEditAutomationController' => 'DiffusionRepositoryEditController',
|
||||
'DiffusionRepositoryEditBasicController' => 'DiffusionRepositoryEditController',
|
||||
'DiffusionRepositoryEditBranchesController' => 'DiffusionRepositoryEditController',
|
||||
'DiffusionRepositoryEditController' => 'DiffusionController',
|
||||
|
@ -4535,6 +4565,17 @@ phutil_register_library_map(array(
|
|||
'DoorkeeperTagsController' => 'PhabricatorController',
|
||||
'DrydockAlmanacServiceHostBlueprintImplementation' => 'DrydockBlueprintImplementation',
|
||||
'DrydockApacheWebrootInterface' => 'DrydockWebrootInterface',
|
||||
'DrydockAuthorization' => array(
|
||||
'DrydockDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
),
|
||||
'DrydockAuthorizationAuthorizeController' => 'DrydockController',
|
||||
'DrydockAuthorizationListController' => 'DrydockController',
|
||||
'DrydockAuthorizationListView' => 'AphrontView',
|
||||
'DrydockAuthorizationPHIDType' => 'PhabricatorPHIDType',
|
||||
'DrydockAuthorizationQuery' => 'DrydockQuery',
|
||||
'DrydockAuthorizationSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'DrydockAuthorizationViewController' => 'DrydockController',
|
||||
'DrydockBlueprint' => array(
|
||||
'DrydockDAO',
|
||||
'PhabricatorApplicationTransactionInterface',
|
||||
|
@ -4576,6 +4617,7 @@ phutil_register_library_map(array(
|
|||
'DrydockDefaultViewCapability' => 'PhabricatorPolicyCapability',
|
||||
'DrydockFilesystemInterface' => 'DrydockInterface',
|
||||
'DrydockInterface' => 'Phobject',
|
||||
'DrydockLandRepositoryOperation' => 'DrydockRepositoryOperationType',
|
||||
'DrydockLease' => array(
|
||||
'DrydockDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
|
@ -4589,6 +4631,8 @@ phutil_register_library_map(array(
|
|||
'DrydockLeaseDestroyedLogType' => 'DrydockLogType',
|
||||
'DrydockLeaseListController' => 'DrydockLeaseController',
|
||||
'DrydockLeaseListView' => 'AphrontView',
|
||||
'DrydockLeaseNoAuthorizationsLogType' => 'DrydockLogType',
|
||||
'DrydockLeaseNoBlueprintsLogType' => 'DrydockLogType',
|
||||
'DrydockLeasePHIDType' => 'PhabricatorPHIDType',
|
||||
'DrydockLeaseQuery' => 'DrydockQuery',
|
||||
'DrydockLeaseQueuedLogType' => 'DrydockLogType',
|
||||
|
@ -4617,7 +4661,19 @@ phutil_register_library_map(array(
|
|||
'DrydockManagementUpdateLeaseWorkflow' => 'DrydockManagementWorkflow',
|
||||
'DrydockManagementUpdateResourceWorkflow' => 'DrydockManagementWorkflow',
|
||||
'DrydockManagementWorkflow' => 'PhabricatorManagementWorkflow',
|
||||
'DrydockObjectAuthorizationView' => 'AphrontView',
|
||||
'DrydockQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'DrydockRepositoryOperation' => array(
|
||||
'DrydockDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
),
|
||||
'DrydockRepositoryOperationListController' => 'DrydockController',
|
||||
'DrydockRepositoryOperationPHIDType' => 'PhabricatorPHIDType',
|
||||
'DrydockRepositoryOperationQuery' => 'DrydockQuery',
|
||||
'DrydockRepositoryOperationSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'DrydockRepositoryOperationType' => 'Phobject',
|
||||
'DrydockRepositoryOperationUpdateWorker' => 'DrydockWorker',
|
||||
'DrydockRepositoryOperationViewController' => 'DrydockController',
|
||||
'DrydockResource' => array(
|
||||
'DrydockDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
|
@ -5301,6 +5357,7 @@ phutil_register_library_map(array(
|
|||
'PHUIPropertyListExample' => 'PhabricatorUIExample',
|
||||
'PHUIPropertyListView' => 'AphrontView',
|
||||
'PHUIRemarkupPreviewPanel' => 'AphrontTagView',
|
||||
'PHUIRemarkupView' => 'AphrontView',
|
||||
'PHUISpacesNamespaceContextView' => 'AphrontView',
|
||||
'PHUIStatusItemView' => 'AphrontTagView',
|
||||
'PHUIStatusListView' => 'AphrontTagView',
|
||||
|
@ -7089,6 +7146,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSpacesTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorSpacesViewController' => 'PhabricatorSpacesController',
|
||||
'PhabricatorStandardCustomField' => 'PhabricatorCustomField',
|
||||
'PhabricatorStandardCustomFieldBlueprints' => 'PhabricatorStandardCustomFieldTokenizer',
|
||||
'PhabricatorStandardCustomFieldBool' => 'PhabricatorStandardCustomField',
|
||||
'PhabricatorStandardCustomFieldCredential' => 'PhabricatorStandardCustomField',
|
||||
'PhabricatorStandardCustomFieldDatasource' => 'PhabricatorStandardCustomFieldTokenizer',
|
||||
|
|
|
@ -372,6 +372,13 @@ abstract class AphrontApplicationConfiguration extends Phobject {
|
|||
$result = $this->routePath($maps, $path.'/');
|
||||
if ($result) {
|
||||
$slash_uri = $request->getRequestURI()->setPath($path.'/');
|
||||
|
||||
// We need to restore URI encoding because the webserver has
|
||||
// interpreted it. For example, this allows us to redirect a path
|
||||
// like `/tag/aa%20bb` to `/tag/aa%20bb/`, which may eventually be
|
||||
// resolved meaningfully by an application.
|
||||
$slash_uri = phutil_escape_uri($slash_uri);
|
||||
|
||||
$external = strlen($request->getRequestURI()->getDomain());
|
||||
return $this->buildRedirectController($slash_uri, $external);
|
||||
}
|
||||
|
|
|
@ -104,14 +104,10 @@ final class PhabricatorBadgesViewController
|
|||
|
||||
$description = $badge->getDescription();
|
||||
if (strlen($description)) {
|
||||
$description = PhabricatorMarkupEngine::renderOneObject(
|
||||
id(new PhabricatorMarkupOneOff())->setContent($description),
|
||||
'default',
|
||||
$viewer);
|
||||
|
||||
$view->addSectionHeader(
|
||||
pht('Description'), PHUIPropertyListView::ICON_SUMMARY);
|
||||
$view->addTextContent($description);
|
||||
$view->addTextContent(
|
||||
new PHUIRemarkupView($viewer, $description));
|
||||
}
|
||||
|
||||
$badge = id(new PHUIBadgeView())
|
||||
|
|
|
@ -195,15 +195,10 @@ final class PhabricatorConduitConsoleController
|
|||
pht('Errors'),
|
||||
$error_description);
|
||||
|
||||
|
||||
$description = $method->getMethodDescription();
|
||||
$description = PhabricatorMarkupEngine::renderOneObject(
|
||||
id(new PhabricatorMarkupOneOff())->setContent($description),
|
||||
'default',
|
||||
$viewer);
|
||||
$view->addSectionHeader(
|
||||
pht('Description'), PHUIPropertyListView::ICON_SUMMARY);
|
||||
$view->addTextContent($description);
|
||||
$view->addTextContent(
|
||||
new PHUIRemarkupView($viewer, $method->getMethodDescription()));
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
|
|
@ -45,8 +45,8 @@ final class PhabricatorPygmentSetupCheck extends PhabricatorSetupCheck {
|
|||
'Phabricator has %s available in %s, but the binary '.
|
||||
'exited with an error code when run as %s. Check that it is '.
|
||||
'installed correctly.',
|
||||
phutil_tag('tt', array(), '$PATH'),
|
||||
phutil_tag('tt', array(), 'pygmentize'),
|
||||
phutil_tag('tt', array(), '$PATH'),
|
||||
phutil_tag('tt', array(), 'pygmentize -h'));
|
||||
|
||||
$this
|
||||
|
|
|
@ -75,6 +75,8 @@ final class PhabricatorDifferentialApplication extends PhabricatorApplication {
|
|||
=> 'DifferentialRevisionCloseDetailsController',
|
||||
'update/(?P<revisionID>[1-9]\d*)/'
|
||||
=> 'DifferentialDiffCreateController',
|
||||
'operation/(?P<id>[1-9]\d*)/'
|
||||
=> 'DifferentialRevisionOperationController',
|
||||
),
|
||||
'comment/' => array(
|
||||
'preview/(?P<id>[1-9]\d*)/' => 'DifferentialCommentPreviewController',
|
||||
|
|
|
@ -3,19 +3,13 @@
|
|||
final class DifferentialCommentPreviewController
|
||||
extends DifferentialController {
|
||||
|
||||
private $id;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->id = $data['id'];
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
$revision = id(new DifferentialRevisionQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($this->id))
|
||||
->withIDs(array($id))
|
||||
->executeOne();
|
||||
if (!$revision) {
|
||||
return new Aphront404Response();
|
||||
|
@ -119,7 +113,7 @@ final class DifferentialCommentPreviewController
|
|||
$metadata['action'] = $action;
|
||||
}
|
||||
|
||||
$draft_key = 'differential-comment-'.$this->id;
|
||||
$draft_key = 'differential-comment-'.$id;
|
||||
$draft = id(new PhabricatorDraft())
|
||||
->setAuthorPHID($viewer->getPHID())
|
||||
->setDraftKey($draft_key)
|
||||
|
|
|
@ -3,15 +3,9 @@
|
|||
final class DifferentialCommentSaveController
|
||||
extends DifferentialController {
|
||||
|
||||
private $id;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->id = $data['id'];
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
if (!$request->isFormPost()) {
|
||||
return new Aphront400Response();
|
||||
|
@ -19,7 +13,7 @@ final class DifferentialCommentSaveController
|
|||
|
||||
$revision = id(new DifferentialRevisionQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($this->id))
|
||||
->withIDs(array($id))
|
||||
->needReviewerStatus(true)
|
||||
->needReviewerAuthority(true)
|
||||
->executeOne();
|
||||
|
|
|
@ -2,23 +2,17 @@
|
|||
|
||||
final class DifferentialDiffViewController extends DifferentialController {
|
||||
|
||||
private $id;
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->id = $data['id'];
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
$diff = id(new DifferentialDiffQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($this->id))
|
||||
->withIDs(array($id))
|
||||
->executeOne();
|
||||
if (!$diff) {
|
||||
return new Aphront404Response();
|
||||
|
|
|
@ -3,20 +3,11 @@
|
|||
final class DifferentialRevisionCloseDetailsController
|
||||
extends DifferentialController {
|
||||
|
||||
private $phid;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->phid = idx($data, 'phid');
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
|
||||
$viewer = $request->getUser();
|
||||
$xaction_phid = $this->phid;
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$xaction = id(new PhabricatorObjectQuery())
|
||||
->withPHIDs(array($xaction_phid))
|
||||
->withPHIDs(array($request->getURIData('phid')))
|
||||
->setViewer($viewer)
|
||||
->executeOne();
|
||||
if (!$xaction) {
|
||||
|
|
|
@ -3,24 +3,18 @@
|
|||
final class DifferentialRevisionEditController
|
||||
extends DifferentialController {
|
||||
|
||||
private $id;
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->id = idx($data, 'id');
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
if (!$this->id) {
|
||||
$this->id = $request->getInt('revisionID');
|
||||
if (!$id) {
|
||||
$id = $request->getInt('revisionID');
|
||||
}
|
||||
|
||||
if ($this->id) {
|
||||
if ($id) {
|
||||
$revision = id(new DifferentialRevisionQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($this->id))
|
||||
->withIDs(array($id))
|
||||
->needRelationships(true)
|
||||
->needReviewerStatus(true)
|
||||
->needActiveDiffs(true)
|
||||
|
|
|
@ -11,9 +11,8 @@ final class DifferentialRevisionLandController extends DifferentialController {
|
|||
$this->strategyClass = $data['strategy'];
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$revision_id = $this->revisionID;
|
||||
|
||||
|
|
|
@ -2,19 +2,13 @@
|
|||
|
||||
final class DifferentialRevisionListController extends DifferentialController {
|
||||
|
||||
private $queryKey;
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->queryKey = idx($data, 'queryKey');
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$controller = id(new PhabricatorApplicationSearchController())
|
||||
->setQueryKey($this->queryKey)
|
||||
->setQueryKey($request->getURIData('queryKey'))
|
||||
->setSearchEngine(new DifferentialRevisionSearchEngine())
|
||||
->setNavigation($this->buildSideNavView());
|
||||
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
<?php
|
||||
|
||||
final class DifferentialRevisionOperationController
|
||||
extends DifferentialController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
$revision = id(new DifferentialRevisionQuery())
|
||||
->withIDs(array($id))
|
||||
->setViewer($viewer)
|
||||
->needActiveDiffs(true)
|
||||
->executeOne();
|
||||
if (!$revision) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$detail_uri = "/D{$id}";
|
||||
|
||||
$repository = $revision->getRepository();
|
||||
if (!$repository) {
|
||||
return $this->rejectOperation(
|
||||
$revision,
|
||||
pht('No Repository'),
|
||||
pht(
|
||||
'This revision is not associated with a known repository. Only '.
|
||||
'revisions associated with a tracked repository can be landed '.
|
||||
'automatically.'));
|
||||
}
|
||||
|
||||
if (!$repository->canPerformAutomation()) {
|
||||
return $this->rejectOperation(
|
||||
$revision,
|
||||
pht('No Repository Automation'),
|
||||
pht(
|
||||
'The repository this revision is associated with ("%s") is not '.
|
||||
'configured to support automation. Configure automation for the '.
|
||||
'repository to enable revisions to be landed automatically.',
|
||||
$repository->getMonogram()));
|
||||
}
|
||||
|
||||
// TODO: At some point we should allow installs to give "land reviewed
|
||||
// code" permission to more users than "push any commit", because it is
|
||||
// a much less powerful operation. For now, just require push so this
|
||||
// doesn't do anything users can't do on their own.
|
||||
$can_push = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$repository,
|
||||
DiffusionPushCapability::CAPABILITY);
|
||||
if (!$can_push) {
|
||||
return $this->rejectOperation(
|
||||
$revision,
|
||||
pht('Unable to Push'),
|
||||
pht(
|
||||
'You do not have permission to push to the repository this '.
|
||||
'revision is associated with ("%s"), so you can not land it.',
|
||||
$repository->getMonogram()));
|
||||
}
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
// NOTE: The operation is locked to the current active diff, so if the
|
||||
// revision is updated before the operation applies nothing sneaky
|
||||
// occurs.
|
||||
|
||||
$diff = $revision->getActiveDiff();
|
||||
|
||||
$op = new DrydockLandRepositoryOperation();
|
||||
|
||||
$operation = DrydockRepositoryOperation::initializeNewOperation($op)
|
||||
->setAuthorPHID($viewer->getPHID())
|
||||
->setObjectPHID($revision->getPHID())
|
||||
->setRepositoryPHID($repository->getPHID())
|
||||
->setRepositoryTarget('branch:master')
|
||||
->setProperty('differential.diffPHID', $diff->getPHID());
|
||||
|
||||
$operation->save();
|
||||
$operation->scheduleUpdate();
|
||||
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI($detail_uri);
|
||||
}
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Land Revision'))
|
||||
->appendParagraph(
|
||||
pht(
|
||||
'In theory, this will do approximately what `arc land` would do. '.
|
||||
'In practice, that is almost certainly not what it will actually '.
|
||||
'do.'))
|
||||
->appendParagraph(
|
||||
pht(
|
||||
'THIS FEATURE IS EXPERIMENTAL AND DANGEROUS! USE IT AT YOUR '.
|
||||
'OWN RISK!'))
|
||||
->addCancelButton($detail_uri)
|
||||
->addSubmitButton(pht('Mutate Repository Unpredictably'));
|
||||
}
|
||||
|
||||
private function rejectOperation(
|
||||
DifferentialRevision $revision,
|
||||
$title,
|
||||
$body) {
|
||||
|
||||
$id = $revision->getID();
|
||||
$detail_uri = "/D{$id}";
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle($title)
|
||||
->appendParagraph($body)
|
||||
->addCancelButton($detail_uri);
|
||||
}
|
||||
|
||||
}
|
|
@ -8,15 +8,11 @@ final class DifferentialRevisionViewController extends DifferentialController {
|
|||
return true;
|
||||
}
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->revisionID = $data['id'];
|
||||
}
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
$this->revisionID = $request->getURIData('id');
|
||||
|
||||
public function processRequest() {
|
||||
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
$viewer_is_anonymous = !$user->isLoggedIn();
|
||||
$viewer_is_anonymous = !$viewer->isLoggedIn();
|
||||
|
||||
$revision = id(new DifferentialRevisionQuery())
|
||||
->withIDs(array($this->revisionID))
|
||||
|
@ -68,7 +64,7 @@ final class DifferentialRevisionViewController extends DifferentialController {
|
|||
$repository = $revision->getRepository();
|
||||
} else {
|
||||
$repository = id(new PhabricatorRepositoryQuery())
|
||||
->setViewer($user)
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($repository_phid))
|
||||
->executeOne();
|
||||
}
|
||||
|
@ -117,7 +113,7 @@ final class DifferentialRevisionViewController extends DifferentialController {
|
|||
$revision->loadCommitPHIDs(),
|
||||
array(
|
||||
$revision->getAuthorPHID(),
|
||||
$user->getPHID(),
|
||||
$viewer->getPHID(),
|
||||
));
|
||||
|
||||
foreach ($revision->getAttached() as $type => $phids) {
|
||||
|
@ -130,7 +126,7 @@ final class DifferentialRevisionViewController extends DifferentialController {
|
|||
$revision,
|
||||
PhabricatorCustomField::ROLE_VIEW);
|
||||
|
||||
$field_list->setViewer($user);
|
||||
$field_list->setViewer($viewer);
|
||||
$field_list->readFieldsFromStorage($revision);
|
||||
|
||||
$warning_handle_map = array();
|
||||
|
@ -174,7 +170,7 @@ final class DifferentialRevisionViewController extends DifferentialController {
|
|||
$new = array_select_keys($changesets, $new_ids);
|
||||
|
||||
$query = id(new DifferentialInlineCommentQuery())
|
||||
->setViewer($user)
|
||||
->setViewer($viewer)
|
||||
->needHidden(true)
|
||||
->withRevisionPHIDs(array($revision->getPHID()));
|
||||
$inlines = $query->execute();
|
||||
|
@ -205,7 +201,7 @@ final class DifferentialRevisionViewController extends DifferentialController {
|
|||
$commit_hashes = array_unique(array_filter($commit_hashes));
|
||||
if ($commit_hashes) {
|
||||
$commits_for_links = id(new DiffusionCommitQuery())
|
||||
->setViewer($user)
|
||||
->setViewer($viewer)
|
||||
->withIdentifiers($commit_hashes)
|
||||
->execute();
|
||||
$commits_for_links = mpull(
|
||||
|
@ -217,7 +213,7 @@ final class DifferentialRevisionViewController extends DifferentialController {
|
|||
}
|
||||
|
||||
$revision_detail = id(new DifferentialRevisionDetailView())
|
||||
->setUser($user)
|
||||
->setUser($viewer)
|
||||
->setRevision($revision)
|
||||
->setDiff(end($diffs))
|
||||
->setCustomFields($field_list)
|
||||
|
@ -239,7 +235,7 @@ final class DifferentialRevisionViewController extends DifferentialController {
|
|||
}
|
||||
|
||||
$revision_detail->setActions($actions);
|
||||
$revision_detail->setUser($user);
|
||||
$revision_detail->setUser($viewer);
|
||||
|
||||
$revision_detail_box = $revision_detail->render();
|
||||
|
||||
|
@ -261,7 +257,7 @@ final class DifferentialRevisionViewController extends DifferentialController {
|
|||
$detail_diffs = mpull($detail_diffs, null, 'getPHID');
|
||||
|
||||
$buildables = id(new HarbormasterBuildableQuery())
|
||||
->setViewer($user)
|
||||
->setViewer($viewer)
|
||||
->withBuildablePHIDs(array_keys($detail_diffs))
|
||||
->withManualBuildables(false)
|
||||
->needBuilds(true)
|
||||
|
@ -311,7 +307,7 @@ final class DifferentialRevisionViewController extends DifferentialController {
|
|||
'/differential/changeset/?view=old',
|
||||
'/differential/changeset/?view=new');
|
||||
|
||||
$changeset_view->setUser($user);
|
||||
$changeset_view->setUser($viewer);
|
||||
$changeset_view->setDiff($target);
|
||||
$changeset_view->setRenderingReferences($rendering_references);
|
||||
$changeset_view->setVsMap($vs_map);
|
||||
|
@ -323,7 +319,7 @@ final class DifferentialRevisionViewController extends DifferentialController {
|
|||
$changeset_view->setTitle(pht('Diff %s', $target->getID()));
|
||||
|
||||
$diff_history = id(new DifferentialRevisionUpdateHistoryView())
|
||||
->setUser($user)
|
||||
->setUser($viewer)
|
||||
->setDiffs($diffs)
|
||||
->setSelectedVersusDiffID($diff_vs)
|
||||
->setSelectedDiffID($target->getID())
|
||||
|
@ -331,7 +327,7 @@ final class DifferentialRevisionViewController extends DifferentialController {
|
|||
->setCommitsForLinks($commits_for_links);
|
||||
|
||||
$local_view = id(new DifferentialLocalCommitsView())
|
||||
->setUser($user)
|
||||
->setUser($viewer)
|
||||
->setLocalCommits(idx($props, 'local:commits'))
|
||||
->setCommitsForLinks($commits_for_links);
|
||||
|
||||
|
@ -352,13 +348,13 @@ final class DifferentialRevisionViewController extends DifferentialController {
|
|||
$toc_view = $this->buildTableOfContents(
|
||||
$changesets,
|
||||
$visible_changesets,
|
||||
$target->loadCoverageMap($user));
|
||||
$target->loadCoverageMap($viewer));
|
||||
|
||||
$comment_form = null;
|
||||
if (!$viewer_is_anonymous) {
|
||||
$draft = id(new PhabricatorDraft())->loadOneWhere(
|
||||
'authorPHID = %s AND draftKey = %s',
|
||||
$user->getPHID(),
|
||||
$viewer->getPHID(),
|
||||
'differential-comment-'.$revision->getID());
|
||||
|
||||
$reviewers = array();
|
||||
|
@ -394,7 +390,7 @@ final class DifferentialRevisionViewController extends DifferentialController {
|
|||
'comment/save/'.$revision->getID().'/');
|
||||
|
||||
$comment_form->setActionURI($action_uri);
|
||||
$comment_form->setUser($user);
|
||||
$comment_form->setUser($viewer);
|
||||
$comment_form->setDraft($draft);
|
||||
$comment_form->setReviewers(mpull($reviewers, 'getFullName', 'getPHID'));
|
||||
$comment_form->setCCs(mpull($ccs, 'getFullName', 'getPHID'));
|
||||
|
@ -461,13 +457,16 @@ final class DifferentialRevisionViewController extends DifferentialController {
|
|||
// TODO: For now, just use this to get "Login to Comment".
|
||||
$page_pane->appendChild(
|
||||
id(new PhabricatorApplicationTransactionCommentView())
|
||||
->setUser($user)
|
||||
->setUser($viewer)
|
||||
->setRequestURI($request->getRequestURI()));
|
||||
}
|
||||
|
||||
$object_id = 'D'.$revision->getID();
|
||||
|
||||
$operations_box = $this->buildOperationsBox($revision);
|
||||
|
||||
$content = array(
|
||||
$operations_box,
|
||||
$revision_detail_box,
|
||||
$diff_detail_box,
|
||||
$page_pane,
|
||||
|
@ -476,7 +475,7 @@ final class DifferentialRevisionViewController extends DifferentialController {
|
|||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb($object_id, '/'.$object_id);
|
||||
|
||||
$prefs = $user->loadPreferences();
|
||||
$prefs = $viewer->loadPreferences();
|
||||
|
||||
$pref_filetree = PhabricatorUserPreferences::PREFERENCE_DIFF_FILETREE;
|
||||
if ($prefs->getPreference($pref_filetree)) {
|
||||
|
@ -1036,4 +1035,55 @@ final class DifferentialRevisionViewController extends DifferentialController {
|
|||
return $view;
|
||||
}
|
||||
|
||||
private function buildOperationsBox(DifferentialRevision $revision) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
// Save a query if we can't possibly have pending operations.
|
||||
$repository = $revision->getRepository();
|
||||
if (!$repository || !$repository->canPerformAutomation()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$operations = id(new DrydockRepositoryOperationQuery())
|
||||
->setViewer($viewer)
|
||||
->withObjectPHIDs(array($revision->getPHID()))
|
||||
->withOperationStates(
|
||||
array(
|
||||
DrydockRepositoryOperation::STATE_WAIT,
|
||||
DrydockRepositoryOperation::STATE_WORK,
|
||||
DrydockRepositoryOperation::STATE_FAIL,
|
||||
))
|
||||
->execute();
|
||||
if (!$operations) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$operation = head(msort($operations, 'getID'));
|
||||
|
||||
// TODO: This is completely made up for now, give it useful information and
|
||||
// a sweet progress bar.
|
||||
|
||||
switch ($operation->getOperationState()) {
|
||||
case DrydockRepositoryOperation::STATE_WAIT:
|
||||
case DrydockRepositoryOperation::STATE_WORK:
|
||||
$severity = PHUIInfoView::SEVERITY_NOTICE;
|
||||
$text = pht(
|
||||
'Some sort of repository operation is currently running.');
|
||||
break;
|
||||
default:
|
||||
$severity = PHUIInfoView::SEVERITY_ERROR;
|
||||
$text = pht(
|
||||
'Some sort of repository operation failed.');
|
||||
break;
|
||||
}
|
||||
|
||||
$info_view = id(new PHUIInfoView())
|
||||
->setSeverity($severity)
|
||||
->appendChild($text);
|
||||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Active Operations (EXPERIMENTAL!)'))
|
||||
->setInfoView($info_view);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -37,6 +37,18 @@ final class DifferentialLandingActionMenuEventListener
|
|||
return null;
|
||||
}
|
||||
|
||||
if ($repository->canPerformAutomation()) {
|
||||
$revision_id = $revision->getID();
|
||||
|
||||
$action = id(new PhabricatorActionView())
|
||||
->setWorkflow(true)
|
||||
->setName(pht('Land Revision'))
|
||||
->setIcon('fa-fighter-jet')
|
||||
->setHref("/differential/revision/operation/{$revision_id}/");
|
||||
|
||||
$this->addActionMenuItems($event, $action);
|
||||
}
|
||||
|
||||
$strategies = id(new PhutilClassMapQuery())
|
||||
->setAncestorClass('DifferentialLandingStrategy')
|
||||
->execute();
|
||||
|
|
|
@ -251,6 +251,12 @@ final class DifferentialDiff
|
|||
|
||||
$dict['changes'] = $this->buildChangesList();
|
||||
|
||||
return $dict + $this->getDiffAuthorshipDict();
|
||||
}
|
||||
|
||||
public function getDiffAuthorshipDict() {
|
||||
$dict = array();
|
||||
|
||||
$properties = id(new DifferentialDiffProperty())->loadAllWhere(
|
||||
'diffID = %d',
|
||||
$this->getID());
|
||||
|
@ -447,12 +453,8 @@ final class DifferentialDiff
|
|||
$results['repository.vcs'] = $repo->getVersionControlSystem();
|
||||
$results['repository.uri'] = $repo->getPublicCloneURI();
|
||||
|
||||
// TODO: We're just hoping to get lucky. Instead, `arc` should store
|
||||
// where it sent changes and we should only provide staging details
|
||||
// if we reasonably believe they are accurate.
|
||||
$staging_ref = 'refs/tags/phabricator/diff/'.$this->getID();
|
||||
$results['repository.staging.uri'] = $repo->getStagingURI();
|
||||
$results['repository.staging.ref'] = $staging_ref;
|
||||
$results['repository.staging.ref'] = $this->getStagingRef();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -480,6 +482,13 @@ final class DifferentialDiff
|
|||
);
|
||||
}
|
||||
|
||||
public function getStagingRef() {
|
||||
// TODO: We're just hoping to get lucky. Instead, `arc` should store
|
||||
// where it sent changes and we should only provide staging details
|
||||
// if we reasonably believe they are accurate.
|
||||
return 'refs/tags/phabricator/diff/'.$this->getID();
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
||||
|
||||
|
|
|
@ -102,6 +102,7 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication {
|
|||
'update/' => 'DiffusionRepositoryEditUpdateController',
|
||||
'symbol/' => 'DiffusionRepositorySymbolsController',
|
||||
'staging/' => 'DiffusionRepositoryEditStagingController',
|
||||
'automation/' => 'DiffusionRepositoryEditAutomationController',
|
||||
),
|
||||
'pathtree/(?P<dblob>.*)' => 'DiffusionPathTreeController',
|
||||
'mirror/' => array(
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionRepositoryEditAutomationController
|
||||
extends DiffusionRepositoryEditController {
|
||||
|
||||
protected function processDiffusionRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getUser();
|
||||
$drequest = $this->diffusionRequest;
|
||||
$repository = $drequest->getRepository();
|
||||
|
||||
$repository = id(new PhabricatorRepositoryQuery())
|
||||
->setViewer($viewer)
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->withIDs(array($repository->getID()))
|
||||
->executeOne();
|
||||
if (!$repository) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
if (!$repository->supportsAutomation()) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$edit_uri = $this->getRepositoryControllerURI($repository, 'edit/');
|
||||
|
||||
$v_blueprints = $repository->getHumanReadableDetail(
|
||||
'automation.blueprintPHIDs');
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$v_blueprints = $request->getArr('blueprintPHIDs');
|
||||
|
||||
$xactions = array();
|
||||
$template = id(new PhabricatorRepositoryTransaction());
|
||||
|
||||
$type_blueprints =
|
||||
PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS;
|
||||
|
||||
$xactions[] = id(clone $template)
|
||||
->setTransactionType($type_blueprints)
|
||||
->setNewValue($v_blueprints);
|
||||
|
||||
id(new PhabricatorRepositoryEditor())
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContentSourceFromRequest($request)
|
||||
->setActor($viewer)
|
||||
->applyTransactions($repository, $xactions);
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($edit_uri);
|
||||
}
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(pht('Edit Automation'));
|
||||
|
||||
$title = pht('Edit %s', $repository->getName());
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
->appendRemarkupInstructions(
|
||||
pht(
|
||||
"Configure **Repository Automation** to allow Phabricator to ".
|
||||
"write to this repository.".
|
||||
"\n\n".
|
||||
"IMPORTANT: This feature is new, experimental, and not supported. ".
|
||||
"Use it at your own risk."))
|
||||
->appendControl(
|
||||
id(new AphrontFormTokenizerControl())
|
||||
->setLabel(pht('Use Blueprints'))
|
||||
->setName('blueprintPHIDs')
|
||||
->setValue($v_blueprints)
|
||||
->setDatasource(new DrydockBlueprintDatasource()))
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->setValue(pht('Save'))
|
||||
->addCancelButton($edit_uri));
|
||||
|
||||
$object_box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText($title)
|
||||
->setForm($form);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
array(
|
||||
$crumbs,
|
||||
$object_box,
|
||||
),
|
||||
array(
|
||||
'title' => $title,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
|
@ -31,6 +31,7 @@ final class DiffusionRepositoryEditMainController
|
|||
$has_branches = ($is_git || $is_hg);
|
||||
$has_local = $repository->usesLocalWorkingCopy();
|
||||
$supports_staging = $repository->supportsStaging();
|
||||
$supports_automation = $repository->supportsAutomation();
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs($is_main = true);
|
||||
|
||||
|
@ -100,6 +101,13 @@ final class DiffusionRepositoryEditMainController
|
|||
$this->buildStagingActions($repository));
|
||||
}
|
||||
|
||||
$automation_properties = null;
|
||||
if ($supports_automation) {
|
||||
$automation_properties = $this->buildAutomationProperties(
|
||||
$repository,
|
||||
$this->buildAutomationActions($repository));
|
||||
}
|
||||
|
||||
$actions_properties = $this->buildActionsProperties(
|
||||
$repository,
|
||||
$this->buildActionsActions($repository));
|
||||
|
@ -171,6 +179,12 @@ final class DiffusionRepositoryEditMainController
|
|||
->addPropertyList($staging_properties);
|
||||
}
|
||||
|
||||
if ($automation_properties) {
|
||||
$boxes[] = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Automation'))
|
||||
->addPropertyList($automation_properties);
|
||||
}
|
||||
|
||||
$boxes[] = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Text Encoding'))
|
||||
->addPropertyList($encoding_properties);
|
||||
|
@ -622,7 +636,6 @@ final class DiffusionRepositoryEditMainController
|
|||
return $view;
|
||||
}
|
||||
|
||||
|
||||
private function buildStagingActions(PhabricatorRepository $repository) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
|
@ -661,6 +674,47 @@ final class DiffusionRepositoryEditMainController
|
|||
return $view;
|
||||
}
|
||||
|
||||
private function buildAutomationActions(PhabricatorRepository $repository) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$view = id(new PhabricatorActionListView())
|
||||
->setObjectURI($this->getRequest()->getRequestURI())
|
||||
->setUser($viewer);
|
||||
|
||||
$edit = id(new PhabricatorActionView())
|
||||
->setIcon('fa-pencil')
|
||||
->setName(pht('Edit Automation'))
|
||||
->setHref(
|
||||
$this->getRepositoryControllerURI($repository, 'edit/automation/'));
|
||||
$view->addAction($edit);
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
private function buildAutomationProperties(
|
||||
PhabricatorRepository $repository,
|
||||
PhabricatorActionListView $actions) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$view = id(new PHUIPropertyListView())
|
||||
->setUser($viewer)
|
||||
->setActionList($actions);
|
||||
|
||||
$blueprint_phids = $repository->getAutomationBlueprintPHIDs();
|
||||
if (!$blueprint_phids) {
|
||||
$blueprint_view = phutil_tag('em', array(), pht('Not Configured'));
|
||||
} else {
|
||||
$blueprint_view = id(new DrydockObjectAuthorizationView())
|
||||
->setUser($viewer)
|
||||
->setObjectPHID($repository->getPHID())
|
||||
->setBlueprintPHIDs($blueprint_phids);
|
||||
}
|
||||
|
||||
$view->addProperty(pht('Automation'), $blueprint_view);
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
private function buildHostingActions(PhabricatorRepository $repository) {
|
||||
$user = $this->getRequest()->getUser();
|
||||
|
||||
|
|
|
@ -536,12 +536,14 @@ final class DiffusionServeController extends DiffusionController {
|
|||
$body = strlen($stderr)."\n".$stderr;
|
||||
} else {
|
||||
list($length, $body) = explode("\n", $stdout, 2);
|
||||
if ($cmd == 'capabilities') {
|
||||
$body = DiffusionMercurialWireProtocol::filterBundle2Capability($body);
|
||||
}
|
||||
}
|
||||
|
||||
return id(new DiffusionMercurialResponse())->setContent($body);
|
||||
}
|
||||
|
||||
|
||||
private function getMercurialArguments() {
|
||||
// Mercurial sends arguments in HTTP headers. "Why?", you might wonder,
|
||||
// "Why would you do this?".
|
||||
|
|
|
@ -99,4 +99,34 @@ final class DiffusionMercurialWireProtocol extends Phobject {
|
|||
return true;
|
||||
}
|
||||
|
||||
/** If the server version is running 3.4+ it will respond
|
||||
* with 'bundle2' capability in the format of "bundle2=(url-encoding)".
|
||||
* Until we maange to properly package up bundles to send back we
|
||||
* disallow the client from knowing we speak bundle2 by removing it
|
||||
* from the capabilities listing.
|
||||
*
|
||||
* The format of the capabilties string is: "a space separated list
|
||||
* of strings representing what commands the server supports"
|
||||
* @link https://www.mercurial-scm.org/wiki/CommandServer#Protocol
|
||||
*
|
||||
* @param string $capabilities - The string of capabilities to
|
||||
* strip the bundle2 capability from. This is expected to be
|
||||
* the space-separated list of strings resulting from the
|
||||
* querying the 'capabilties' command.
|
||||
*
|
||||
* @return string The resulting space-separated list of capabilities
|
||||
* which no longer contains the 'bundle2' capability. This is meant
|
||||
* to replace the original $body to send back to client.
|
||||
*/
|
||||
public static function filterBundle2Capability($capabilities) {
|
||||
$parts = explode(' ', $capabilities);
|
||||
foreach ($parts as $key => $part) {
|
||||
if (preg_match('/^bundle2=/', $part)) {
|
||||
unset($parts[$key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return implode(' ', $parts);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionMercurialWireProtocolTests extends PhabricatorTestCase {
|
||||
|
||||
public function testFilteringBundle2Capability() {
|
||||
// this was the result of running 'capabilities' over
|
||||
// `hg serve --stdio` on my systems with Mercurial 3.5.1, 2.6.2
|
||||
|
||||
$capabilities_with_bundle2_hg_351 =
|
||||
'lookup changegroupsubset branchmap pushkey '.
|
||||
'known getbundle unbundlehash batch stream '.
|
||||
'bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512'.
|
||||
'%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0A'.
|
||||
'hgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps '.
|
||||
'unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024';
|
||||
|
||||
$capabilities_without_bundle2_hg_351 =
|
||||
'lookup changegroupsubset branchmap pushkey '.
|
||||
'known getbundle unbundlehash batch stream '.
|
||||
'unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024';
|
||||
|
||||
$capabilities_hg_262 =
|
||||
'lookup changegroupsubset branchmap pushkey '.
|
||||
'known getbundle unbundlehash batch stream '.
|
||||
'unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 largefiles=serve';
|
||||
|
||||
$cases = array(
|
||||
array(
|
||||
'name' => pht('Filter bundle2 from Mercurial 3.5.1'),
|
||||
'input' => $capabilities_with_bundle2_hg_351,
|
||||
'expect' => $capabilities_without_bundle2_hg_351,
|
||||
),
|
||||
|
||||
array(
|
||||
'name' => pht('Filter bundle does not affect Mercurial 2.6.2'),
|
||||
'input' => $capabilities_hg_262,
|
||||
'expect' => $capabilities_hg_262,
|
||||
),
|
||||
);
|
||||
|
||||
foreach ($cases as $case) {
|
||||
$actual = DiffusionMercurialWireProtocol::filterBundle2Capability(
|
||||
$case['input']);
|
||||
$this->assertEqual($case['expect'], $actual, $case['name']);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -24,10 +24,17 @@ final class DiffusionLowLevelMercurialPathsQuery
|
|||
$path = $this->path;
|
||||
$commit = $this->commit;
|
||||
|
||||
$hg_paths_command = 'locate --print0 --rev %s -I %s';
|
||||
$hg_version = PhabricatorRepositoryVersion::getMercurialVersion();
|
||||
if (PhabricatorRepositoryVersion::isMercurialFilesCommandAvailable(
|
||||
$hg_version)) {
|
||||
$hg_paths_command = 'files --print0 --rev %s -I %s';
|
||||
}
|
||||
|
||||
$match_against = trim($path, '/');
|
||||
$prefix = trim('./'.$match_against, '/');
|
||||
list($entire_manifest) = $repository->execxLocalCommand(
|
||||
'locate --print0 --rev %s -I %s',
|
||||
$hg_paths_command,
|
||||
hgsprintf('%s', $commit),
|
||||
$prefix);
|
||||
return explode("\0", $entire_manifest);
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionLowLevelMercurialPathsQueryTests
|
||||
extends PhabricatorTestCase {
|
||||
|
||||
public function testCommandByVersion() {
|
||||
$cases = array(
|
||||
array(
|
||||
'name' => pht('Versions which should not use `files`'),
|
||||
'versions' => array('2.6.2', '2.9', '3.1'),
|
||||
'match' => false,
|
||||
),
|
||||
|
||||
array(
|
||||
'name' => pht('Versions which should use `files`'),
|
||||
'versions' => array('3.2', '3.3', '3.5.2'),
|
||||
'match' => true,
|
||||
),
|
||||
);
|
||||
|
||||
foreach ($cases as $case) {
|
||||
foreach ($case['versions'] as $version) {
|
||||
$actual = PhabricatorRepositoryVersion
|
||||
::isMercurialFilesCommandAvailable($version);
|
||||
$expect = $case['match'];
|
||||
$this->assertEqual($expect, $actual, $case['name']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -107,8 +107,14 @@ final class DiffusionMercurialServeSSHWorkflow
|
|||
$this->didSeeWrite = true;
|
||||
}
|
||||
|
||||
$raw_message = $message['raw'];
|
||||
if ($command == 'capabilities') {
|
||||
$raw_message = DiffusionMercurialWireProtocol::filterBundle2Capability(
|
||||
$raw_message);
|
||||
}
|
||||
|
||||
// If we're good, return the raw message data.
|
||||
return $message['raw'];
|
||||
return $raw_message;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -57,6 +57,8 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication {
|
|||
'DrydockResourceListController',
|
||||
'logs/(?:query/(?P<queryKey>[^/]+)/)?' =>
|
||||
'DrydockLogListController',
|
||||
'authorizations/(?:query/(?P<queryKey>[^/]+)/)?' =>
|
||||
'DrydockAuthorizationListController',
|
||||
),
|
||||
'create/' => 'DrydockBlueprintCreateController',
|
||||
'edit/(?:(?P<id>[1-9]\d*)/)?' => 'DrydockBlueprintEditController',
|
||||
|
@ -81,6 +83,20 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication {
|
|||
'DrydockLogListController',
|
||||
),
|
||||
),
|
||||
'(?P<type>authorization)/' => array(
|
||||
'(?P<id>[1-9]\d*)/' => array(
|
||||
'' => 'DrydockAuthorizationViewController',
|
||||
'(?P<action>authorize|decline)/' =>
|
||||
'DrydockAuthorizationAuthorizeController',
|
||||
),
|
||||
),
|
||||
'(?P<type>operation)/' => array(
|
||||
'(?:query/(?P<queryKey>[^/]+)/)?'
|
||||
=> 'DrydockRepositoryOperationListController',
|
||||
'(?P<id>[1-9]\d*)/' => array(
|
||||
'' => 'DrydockRepositoryOperationViewController',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -292,7 +292,8 @@ abstract class DrydockBlueprintImplementation extends Phobject {
|
|||
}
|
||||
|
||||
protected function newLease(DrydockBlueprint $blueprint) {
|
||||
return DrydockLease::initializeNewLease();
|
||||
return DrydockLease::initializeNewLease()
|
||||
->setAuthorizingPHID($blueprint->getPHID());
|
||||
}
|
||||
|
||||
protected function requireActiveLease(DrydockLease $lease) {
|
||||
|
@ -342,9 +343,12 @@ abstract class DrydockBlueprintImplementation extends Phobject {
|
|||
|
||||
$counts = queryfx_all(
|
||||
$conn_r,
|
||||
'SELECT status, COUNT(*) N FROM %T WHERE blueprintPHID = %s',
|
||||
'SELECT status, COUNT(*) N FROM %T
|
||||
WHERE blueprintPHID = %s AND status != %s
|
||||
GROUP BY status',
|
||||
$resource->getTableName(),
|
||||
$blueprint->getPHID());
|
||||
$blueprint->getPHID(),
|
||||
DrydockResourceStatus::STATUS_DESTROYED);
|
||||
$counts = ipull($counts, 'N', 'status');
|
||||
|
||||
$n_alloc = idx($counts, DrydockResourceStatus::STATUS_PENDING, 0);
|
||||
|
|
|
@ -118,10 +118,13 @@ final class DrydockWorkingCopyBlueprintImplementation
|
|||
|
||||
$resource_phid = $resource->getPHID();
|
||||
|
||||
$blueprint_phids = $blueprint->getFieldValue('blueprintPHIDs');
|
||||
|
||||
$host_lease = $this->newLease($blueprint)
|
||||
->setResourceType('host')
|
||||
->setOwnerPHID($resource_phid)
|
||||
->setAttribute('workingcopy.resourcePHID', $resource_phid);
|
||||
->setAttribute('workingcopy.resourcePHID', $resource_phid)
|
||||
->setAllowedBlueprintPHIDs($blueprint_phids);
|
||||
|
||||
$resource
|
||||
->setAttribute('host.leasePHID', $host_lease->getPHID())
|
||||
|
@ -390,5 +393,15 @@ final class DrydockWorkingCopyBlueprintImplementation
|
|||
return $lease;
|
||||
}
|
||||
|
||||
public function getFieldSpecifications() {
|
||||
return array(
|
||||
'blueprintPHIDs' => array(
|
||||
'name' => pht('Use Blueprints'),
|
||||
'type' => 'blueprints',
|
||||
'required' => true,
|
||||
),
|
||||
) + parent::getFieldSpecifications();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
final class DrydockAuthorizationAuthorizeController
|
||||
extends DrydockController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
$authorization = id(new DrydockAuthorizationQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->executeOne();
|
||||
if (!$authorization) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$authorization_uri = $this->getApplicationURI("authorization/{$id}/");
|
||||
$is_authorize = ($request->getURIData('action') == 'authorize');
|
||||
|
||||
$state_authorized = DrydockAuthorization::BLUEPRINTAUTH_AUTHORIZED;
|
||||
$state_declined = DrydockAuthorization::BLUEPRINTAUTH_DECLINED;
|
||||
|
||||
$state = $authorization->getBlueprintAuthorizationState();
|
||||
$can_authorize = ($state != $state_authorized);
|
||||
$can_decline = ($state != $state_declined);
|
||||
|
||||
if ($is_authorize && !$can_authorize) {
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Already Authorized'))
|
||||
->appendParagraph(
|
||||
pht(
|
||||
'This authorization has already been approved.'))
|
||||
->addCancelButton($authorization_uri);
|
||||
}
|
||||
|
||||
if (!$is_authorize && !$can_decline) {
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Already Declined'))
|
||||
->appendParagraph(
|
||||
pht('This authorization has already been declined.'))
|
||||
->addCancelButton($authorization_uri);
|
||||
}
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
if ($is_authorize) {
|
||||
$new_state = $state_authorized;
|
||||
} else {
|
||||
$new_state = $state_declined;
|
||||
}
|
||||
|
||||
$authorization
|
||||
->setBlueprintAuthorizationState($new_state)
|
||||
->save();
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($authorization_uri);
|
||||
}
|
||||
|
||||
if ($is_authorize) {
|
||||
$title = pht('Approve Authorization');
|
||||
$body = pht(
|
||||
'Approve this authorization? The object will be able to lease and '.
|
||||
'allocate resources created by this blueprint.');
|
||||
$button = pht('Approve Authorization');
|
||||
} else {
|
||||
$title = pht('Decline Authorization');
|
||||
$body = pht(
|
||||
'Decline this authorization? The object will not be able to lease '.
|
||||
'or allocate resources created by this blueprint.');
|
||||
$button = pht('Decline Authorization');
|
||||
}
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle($title)
|
||||
->appendParagraph($body)
|
||||
->addSubmitButton($button)
|
||||
->addCancelButton($authorization_uri);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
final class DrydockAuthorizationListController
|
||||
extends DrydockController {
|
||||
|
||||
private $blueprint;
|
||||
|
||||
public function setBlueprint(DrydockBlueprint $blueprint) {
|
||||
$this->blueprint = $blueprint;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBlueprint() {
|
||||
return $this->blueprint;
|
||||
}
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$engine = new DrydockAuthorizationSearchEngine();
|
||||
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
$blueprint = id(new DrydockBlueprintQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->executeOne();
|
||||
if (!$blueprint) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$this->setBlueprint($blueprint);
|
||||
$engine->setBlueprint($blueprint);
|
||||
|
||||
$querykey = $request->getURIData('queryKey');
|
||||
|
||||
$controller = id(new PhabricatorApplicationSearchController())
|
||||
->setQueryKey($querykey)
|
||||
->setSearchEngine($engine)
|
||||
->setNavigation($this->buildSideNavView());
|
||||
|
||||
return $this->delegateToController($controller);
|
||||
}
|
||||
|
||||
public function buildSideNavView() {
|
||||
$nav = new AphrontSideNavFilterView();
|
||||
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
|
||||
|
||||
$engine = id(new DrydockAuthorizationSearchEngine())
|
||||
->setViewer($this->getViewer());
|
||||
|
||||
$engine->setBlueprint($this->getBlueprint());
|
||||
$engine->addNavigationItems($nav->getMenu());
|
||||
|
||||
$nav->selectFilter(null);
|
||||
|
||||
return $nav;
|
||||
}
|
||||
|
||||
protected function buildApplicationCrumbs() {
|
||||
$crumbs = parent::buildApplicationCrumbs();
|
||||
|
||||
$blueprint = $this->getBlueprint();
|
||||
if ($blueprint) {
|
||||
$id = $blueprint->getID();
|
||||
|
||||
$crumbs->addTextCrumb(
|
||||
pht('Blueprints'),
|
||||
$this->getApplicationURI('blueprint/'));
|
||||
|
||||
$crumbs->addTextCrumb(
|
||||
$blueprint->getBlueprintName(),
|
||||
$this->getApplicationURI("blueprint/{$id}/"));
|
||||
|
||||
$crumbs->addTextCrumb(
|
||||
pht('Authorizations'),
|
||||
$this->getApplicationURI("blueprint/{$id}/authorizations/"));
|
||||
}
|
||||
|
||||
return $crumbs;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
|
||||
final class DrydockAuthorizationViewController
|
||||
extends DrydockController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
$authorization = id(new DrydockAuthorizationQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->executeOne();
|
||||
if (!$authorization) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$id = $authorization->getID();
|
||||
$title = pht('Authorization %d', $id);
|
||||
|
||||
$blueprint = $authorization->getBlueprint();
|
||||
$blueprint_id = $blueprint->getID();
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader($title)
|
||||
->setUser($viewer)
|
||||
->setPolicyObject($authorization);
|
||||
|
||||
|
||||
$state = $authorization->getBlueprintAuthorizationState();
|
||||
$icon = DrydockAuthorization::getBlueprintStateIcon($state);
|
||||
$name = DrydockAuthorization::getBlueprintStateName($state);
|
||||
|
||||
$header->setStatus($icon, null, $name);
|
||||
|
||||
$actions = $this->buildActionListView($authorization);
|
||||
$properties = $this->buildPropertyListView($authorization);
|
||||
$properties->setActionList($actions);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(
|
||||
pht('Blueprints'),
|
||||
$this->getApplicationURI('blueprint/'));
|
||||
$crumbs->addTextCrumb(
|
||||
$blueprint->getBlueprintName(),
|
||||
$this->getApplicationURI("blueprint/{$blueprint_id}/"));
|
||||
$crumbs->addTextCrumb($title);
|
||||
|
||||
$object_box = id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->addPropertyList($properties);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
array(
|
||||
$crumbs,
|
||||
$object_box,
|
||||
),
|
||||
array(
|
||||
'title' => $title,
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
private function buildActionListView(DrydockAuthorization $authorization) {
|
||||
$viewer = $this->getViewer();
|
||||
$id = $authorization->getID();
|
||||
|
||||
$view = id(new PhabricatorActionListView())
|
||||
->setUser($viewer)
|
||||
->setObjectURI($this->getRequest()->getRequestURI())
|
||||
->setObject($authorization);
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$authorization,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$authorize_uri = $this->getApplicationURI("authorization/{$id}/authorize/");
|
||||
$decline_uri = $this->getApplicationURI("authorization/{$id}/decline/");
|
||||
|
||||
$state_authorized = DrydockAuthorization::BLUEPRINTAUTH_AUTHORIZED;
|
||||
$state_declined = DrydockAuthorization::BLUEPRINTAUTH_DECLINED;
|
||||
|
||||
$state = $authorization->getBlueprintAuthorizationState();
|
||||
$can_authorize = $can_edit && ($state != $state_authorized);
|
||||
$can_decline = $can_edit && ($state != $state_declined);
|
||||
|
||||
$view->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setHref($authorize_uri)
|
||||
->setName(pht('Approve Authorization'))
|
||||
->setIcon('fa-check')
|
||||
->setWorkflow(true)
|
||||
->setDisabled(!$can_authorize));
|
||||
|
||||
$view->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setHref($decline_uri)
|
||||
->setName(pht('Decline Authorization'))
|
||||
->setIcon('fa-times')
|
||||
->setWorkflow(true)
|
||||
->setDisabled(!$can_decline));
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
private function buildPropertyListView(DrydockAuthorization $authorization) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$object_phid = $authorization->getObjectPHID();
|
||||
$handles = $viewer->loadHandles(array($object_phid));
|
||||
$handle = $handles[$object_phid];
|
||||
|
||||
$view = new PHUIPropertyListView();
|
||||
|
||||
$view->addProperty(
|
||||
pht('Authorized Object'),
|
||||
$handle->renderLink($handle->getFullName()));
|
||||
|
||||
$view->addProperty(pht('Object Type'), $handle->getTypeName());
|
||||
|
||||
$object_state = $authorization->getObjectAuthorizationState();
|
||||
|
||||
$view->addProperty(
|
||||
pht('Authorization State'),
|
||||
DrydockAuthorization::getObjectStateName($object_state));
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
}
|
|
@ -51,6 +51,8 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController {
|
|||
|
||||
$resource_box = $this->buildResourceBox($blueprint);
|
||||
|
||||
$authorizations_box = $this->buildAuthorizationsBox($blueprint);
|
||||
|
||||
$timeline = $this->buildTransactionTimeline(
|
||||
$blueprint,
|
||||
new DrydockBlueprintTransactionQuery());
|
||||
|
@ -68,6 +70,7 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController {
|
|||
$crumbs,
|
||||
$object_box,
|
||||
$resource_box,
|
||||
$authorizations_box,
|
||||
$log_box,
|
||||
$timeline,
|
||||
),
|
||||
|
@ -167,12 +170,78 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController {
|
|||
->setTag('a')
|
||||
->setHref($resources_uri)
|
||||
->setIconFont('fa-search')
|
||||
->setText(pht('View All Resources')));
|
||||
->setText(pht('View All')));
|
||||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeader($resource_header)
|
||||
->setObjectList($resource_list);
|
||||
}
|
||||
|
||||
private function buildAuthorizationsBox(DrydockBlueprint $blueprint) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$limit = 25;
|
||||
|
||||
// If there are pending authorizations against this blueprint, make sure
|
||||
// we show them first.
|
||||
|
||||
$pending_authorizations = id(new DrydockAuthorizationQuery())
|
||||
->setViewer($viewer)
|
||||
->withBlueprintPHIDs(array($blueprint->getPHID()))
|
||||
->withObjectStates(
|
||||
array(
|
||||
DrydockAuthorization::OBJECTAUTH_ACTIVE,
|
||||
))
|
||||
->withBlueprintStates(
|
||||
array(
|
||||
DrydockAuthorization::BLUEPRINTAUTH_REQUESTED,
|
||||
))
|
||||
->setLimit($limit)
|
||||
->execute();
|
||||
|
||||
$all_authorizations = id(new DrydockAuthorizationQuery())
|
||||
->setViewer($viewer)
|
||||
->withBlueprintPHIDs(array($blueprint->getPHID()))
|
||||
->withObjectStates(
|
||||
array(
|
||||
DrydockAuthorization::OBJECTAUTH_ACTIVE,
|
||||
))
|
||||
->withBlueprintStates(
|
||||
array(
|
||||
DrydockAuthorization::BLUEPRINTAUTH_REQUESTED,
|
||||
DrydockAuthorization::BLUEPRINTAUTH_AUTHORIZED,
|
||||
))
|
||||
->setLimit($limit)
|
||||
->execute();
|
||||
|
||||
$authorizations =
|
||||
mpull($pending_authorizations, null, 'getPHID') +
|
||||
mpull($all_authorizations, null, 'getPHID');
|
||||
|
||||
$authorization_list = id(new DrydockAuthorizationListView())
|
||||
->setUser($viewer)
|
||||
->setAuthorizations($authorizations)
|
||||
->setNoDataString(
|
||||
pht('No objects have active authorizations to use this blueprint.'));
|
||||
|
||||
$id = $blueprint->getID();
|
||||
$authorizations_uri = "blueprint/{$id}/authorizations/query/all/";
|
||||
$authorizations_uri = $this->getApplicationURI($authorizations_uri);
|
||||
|
||||
$authorizations_header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Active Authorizations'))
|
||||
->addActionLink(
|
||||
id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setHref($authorizations_uri)
|
||||
->setIconFont('fa-search')
|
||||
->setText(pht('View All')));
|
||||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeader($authorizations_header)
|
||||
->setObjectList($authorization_list);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ final class DrydockConsoleController extends DrydockController {
|
|||
$nav->addFilter('blueprint', pht('Blueprints'));
|
||||
$nav->addFilter('resource', pht('Resources'));
|
||||
$nav->addFilter('lease', pht('Leases'));
|
||||
$nav->addFilter('operation', pht('Repository Operations'));
|
||||
|
||||
$nav->selectFilter(null);
|
||||
|
||||
|
@ -52,6 +53,13 @@ final class DrydockConsoleController extends DrydockController {
|
|||
->setHref($this->getApplicationURI('lease/'))
|
||||
->addAttribute(pht('Manage leases on resources.')));
|
||||
|
||||
$menu->addItem(
|
||||
id(new PHUIObjectItemView())
|
||||
->setHeader(pht('Repository Operations'))
|
||||
->setFontIcon('fa-fighter-jet')
|
||||
->setHref($this->getApplicationURI('operation/'))
|
||||
->addAttribute(pht('Review the repository operation queue.')));
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(pht('Console'));
|
||||
|
||||
|
|
|
@ -2,12 +2,6 @@
|
|||
|
||||
abstract class DrydockController extends PhabricatorController {
|
||||
|
||||
abstract public function buildSideNavView();
|
||||
|
||||
public function buildApplicationMenu() {
|
||||
return $this->buildSideNavView()->getMenu();
|
||||
}
|
||||
|
||||
protected function buildLocksTab($owner_phid) {
|
||||
$locks = DrydockSlotLock::loadLocks($owner_phid);
|
||||
|
||||
|
@ -105,7 +99,7 @@ abstract class DrydockController extends PhabricatorController {
|
|||
->setTag('a')
|
||||
->setHref($all_uri)
|
||||
->setIconFont('fa-search')
|
||||
->setText(pht('View All Logs')));
|
||||
->setText(pht('View All')));
|
||||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeader($log_header)
|
||||
|
|
|
@ -116,6 +116,14 @@ final class DrydockLeaseViewController extends DrydockLeaseController {
|
|||
}
|
||||
$view->addProperty(pht('Owner'), $owner_display);
|
||||
|
||||
$authorizing_phid = $lease->getAuthorizingPHID();
|
||||
if ($authorizing_phid) {
|
||||
$authorizing_display = $viewer->renderHandle($authorizing_phid);
|
||||
} else {
|
||||
$authorizing_display = phutil_tag('em', array(), pht('None'));
|
||||
}
|
||||
$view->addProperty(pht('Authorized By'), $authorizing_display);
|
||||
|
||||
$resource_phid = $lease->getResourcePHID();
|
||||
if ($resource_phid) {
|
||||
$resource_display = $viewer->renderHandle($resource_phid);
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
final class DrydockRepositoryOperationListController
|
||||
extends DrydockController {
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$query_key = $request->getURIData('queryKey');
|
||||
|
||||
$engine = new DrydockRepositoryOperationSearchEngine();
|
||||
|
||||
$controller = id(new PhabricatorApplicationSearchController())
|
||||
->setQueryKey($query_key)
|
||||
->setSearchEngine($engine)
|
||||
->setNavigation($this->buildSideNavView());
|
||||
|
||||
return $this->delegateToController($controller);
|
||||
}
|
||||
|
||||
public function buildSideNavView() {
|
||||
$nav = new AphrontSideNavFilterView();
|
||||
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
|
||||
|
||||
$engine = id(new DrydockRepositoryOperationSearchEngine())
|
||||
->setViewer($this->getViewer());
|
||||
|
||||
$engine->addNavigationItems($nav->getMenu());
|
||||
|
||||
$nav->selectFilter(null);
|
||||
|
||||
return $nav;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
final class DrydockRepositoryOperationViewController
|
||||
extends DrydockController {
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
$operation = id(new DrydockRepositoryOperationQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->executeOne();
|
||||
if (!$operation) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$id = $operation->getID();
|
||||
$title = pht('Repository Operation %d', $id);
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader($title)
|
||||
->setUser($viewer)
|
||||
->setPolicyObject($operation);
|
||||
|
||||
$state = $operation->getOperationState();
|
||||
$icon = DrydockRepositoryOperation::getOperationStateIcon($state);
|
||||
$name = DrydockRepositoryOperation::getOperationStateName($state);
|
||||
$header->setStatus($icon, null, $name);
|
||||
|
||||
$actions = $this->buildActionListView($operation);
|
||||
$properties = $this->buildPropertyListView($operation);
|
||||
$properties->setActionList($actions);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(
|
||||
pht('Operations'),
|
||||
$this->getApplicationURI('operation/'));
|
||||
$crumbs->addTextCrumb($title);
|
||||
|
||||
$object_box = id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->addPropertyList($properties);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
array(
|
||||
$crumbs,
|
||||
$object_box,
|
||||
),
|
||||
array(
|
||||
'title' => $title,
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
private function buildActionListView(DrydockRepositoryOperation $operation) {
|
||||
$viewer = $this->getViewer();
|
||||
$id = $operation->getID();
|
||||
|
||||
$view = id(new PhabricatorActionListView())
|
||||
->setUser($viewer)
|
||||
->setObjectURI($this->getRequest()->getRequestURI())
|
||||
->setObject($operation);
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
private function buildPropertyListView(
|
||||
DrydockRepositoryOperation $operation) {
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$view = new PHUIPropertyListView();
|
||||
$view->addProperty(
|
||||
pht('Repository'),
|
||||
$viewer->renderHandle($operation->getRepositoryPHID()));
|
||||
|
||||
$view->addProperty(
|
||||
pht('Object'),
|
||||
$viewer->renderHandle($operation->getObjectPHID()));
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
}
|
|
@ -170,7 +170,7 @@ final class DrydockResourceViewController extends DrydockResourceController {
|
|||
->setTag('a')
|
||||
->setHref($leases_uri)
|
||||
->setIconFont('fa-search')
|
||||
->setText(pht('View All Leases')));
|
||||
->setText(pht('View All')));
|
||||
|
||||
$lease_list = id(new DrydockLeaseListView())
|
||||
->setUser($viewer)
|
||||
|
|
|
@ -41,11 +41,6 @@ final class DrydockBlueprintCoreCustomField
|
|||
$object->setDetail($key, $value);
|
||||
}
|
||||
|
||||
public function applyApplicationTransactionExternalEffects(
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
return;
|
||||
}
|
||||
|
||||
public function getBlueprintFieldValue() {
|
||||
return $this->getProxy()->getFieldValue();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
final class DrydockLeaseNoAuthorizationsLogType extends DrydockLogType {
|
||||
|
||||
const LOGCONST = 'core.lease.no-authorizations';
|
||||
|
||||
public function getLogTypeName() {
|
||||
return pht('No Authorizations');
|
||||
}
|
||||
|
||||
public function getLogTypeIcon(array $data) {
|
||||
return 'fa-map-o red';
|
||||
}
|
||||
|
||||
public function renderLog(array $data) {
|
||||
$viewer = $this->getViewer();
|
||||
$authorizing_phid = idx($data, 'authorizingPHID');
|
||||
|
||||
return pht(
|
||||
'The object which authorized this lease (%s) is not authorized to use '.
|
||||
'any of the blueprints the lease lists. Approve the authorizations '.
|
||||
'before using the lease.',
|
||||
$viewer->renderHandle($authorizing_phid)->render());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
final class DrydockLeaseNoBlueprintsLogType extends DrydockLogType {
|
||||
|
||||
const LOGCONST = 'core.lease.no-blueprints';
|
||||
|
||||
public function getLogTypeName() {
|
||||
return pht('No Blueprints');
|
||||
}
|
||||
|
||||
public function getLogTypeIcon(array $data) {
|
||||
return 'fa-map-o red';
|
||||
}
|
||||
|
||||
public function renderLog(array $data) {
|
||||
return pht('This lease does not list any usable blueprints.');
|
||||
}
|
||||
|
||||
}
|
|
@ -28,7 +28,7 @@ final class DrydockManagementLeaseWorkflow
|
|||
}
|
||||
|
||||
public function execute(PhutilArgumentParser $args) {
|
||||
$console = PhutilConsole::getConsole();
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$resource_type = $args->getArg('type');
|
||||
if (!$resource_type) {
|
||||
|
@ -59,6 +59,23 @@ final class DrydockManagementLeaseWorkflow
|
|||
$lease = id(new DrydockLease())
|
||||
->setResourceType($resource_type);
|
||||
|
||||
$drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
|
||||
$lease->setAuthorizingPHID($drydock_phid);
|
||||
|
||||
// TODO: This is not hugely scalable, although this is a debugging workflow
|
||||
// so maybe it's fine. Do we even need `bin/drydock lease` in the long run?
|
||||
$all_blueprints = id(new DrydockBlueprintQuery())
|
||||
->setViewer($viewer)
|
||||
->execute();
|
||||
$allowed_phids = mpull($all_blueprints, 'getPHID');
|
||||
if (!$allowed_phids) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'No blueprints exist which can plausibly allocate resources to '.
|
||||
'satisfy the requested lease.'));
|
||||
}
|
||||
$lease->setAllowedBlueprintPHIDs($allowed_phids);
|
||||
|
||||
if ($attributes) {
|
||||
$lease->setAttributes($attributes);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
|
||||
final class DrydockLandRepositoryOperation
|
||||
extends DrydockRepositoryOperationType {
|
||||
|
||||
const OPCONST = 'land';
|
||||
|
||||
public function getOperationDescription(
|
||||
DrydockRepositoryOperation $operation,
|
||||
PhabricatorUser $viewer) {
|
||||
return pht('Land Revision');
|
||||
}
|
||||
|
||||
public function applyOperation(
|
||||
DrydockRepositoryOperation $operation,
|
||||
DrydockInterface $interface) {
|
||||
$viewer = $this->getViewer();
|
||||
$repository = $operation->getRepository();
|
||||
|
||||
$cmd = array();
|
||||
$arg = array();
|
||||
|
||||
$object = $operation->getObject();
|
||||
if ($object instanceof DifferentialRevision) {
|
||||
$revision = $object;
|
||||
|
||||
$diff_phid = $operation->getProperty('differential.diffPHID');
|
||||
|
||||
$diff = id(new DifferentialDiffQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($diff_phid))
|
||||
->executeOne();
|
||||
if (!$diff) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Unable to load diff "%s".',
|
||||
$diff_phid));
|
||||
}
|
||||
|
||||
$diff_revid = $diff->getRevisionID();
|
||||
$revision_id = $revision->getID();
|
||||
if ($diff_revid != $revision_id) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Diff ("%s") has wrong revision ID ("%s", expected "%s").',
|
||||
$diff_phid,
|
||||
$diff_revid,
|
||||
$revision_id));
|
||||
}
|
||||
|
||||
$cmd[] = 'git fetch --no-tags -- %s +%s:%s';
|
||||
$arg[] = $repository->getStagingURI();
|
||||
$arg[] = $diff->getStagingRef();
|
||||
$arg[] = $diff->getStagingRef();
|
||||
|
||||
$merge_src = $diff->getStagingRef();
|
||||
|
||||
$dict = $diff->getDiffAuthorshipDict();
|
||||
$author_name = idx($dict, 'authorName');
|
||||
$author_email = idx($dict, 'authorEmail');
|
||||
|
||||
$api_method = 'differential.getcommitmessage';
|
||||
$api_params = array(
|
||||
'revision_id' => $revision->getID(),
|
||||
);
|
||||
|
||||
$commit_message = id(new ConduitCall($api_method, $api_params))
|
||||
->setUser($viewer)
|
||||
->execute();
|
||||
} else {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Invalid or unknown object ("%s") for land operation, expected '.
|
||||
'Differential Revision.',
|
||||
$operation->getObjectPHID()));
|
||||
}
|
||||
|
||||
$target = $operation->getRepositoryTarget();
|
||||
list($type, $name) = explode(':', $target, 2);
|
||||
switch ($type) {
|
||||
case 'branch':
|
||||
$push_dst = 'refs/heads/'.$name;
|
||||
$merge_dst = 'refs/remotes/origin/'.$name;
|
||||
break;
|
||||
default:
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Unknown repository operation target type "%s" (in target "%s").',
|
||||
$type,
|
||||
$target));
|
||||
}
|
||||
|
||||
$committer_info = $this->getCommitterInfo($operation);
|
||||
|
||||
$cmd[] = 'git checkout %s';
|
||||
$arg[] = $merge_dst;
|
||||
|
||||
$cmd[] = 'git merge --no-stat --squash --ff-only -- %s';
|
||||
$arg[] = $merge_src;
|
||||
|
||||
$cmd[] = 'git -c user.name=%s -c user.email=%s commit --author %s -m %s';
|
||||
|
||||
$arg[] = $committer_info['name'];
|
||||
$arg[] = $committer_info['email'];
|
||||
|
||||
$arg[] = "{$author_name} <{$author_email}>";
|
||||
$arg[] = $commit_message;
|
||||
|
||||
$cmd[] = 'git push origin -- %s:%s';
|
||||
$arg[] = 'HEAD';
|
||||
$arg[] = $push_dst;
|
||||
|
||||
$cmd = implode(' && ', $cmd);
|
||||
$argv = array_merge(array($cmd), $arg);
|
||||
|
||||
$result = call_user_func_array(
|
||||
array($interface, 'execx'),
|
||||
$argv);
|
||||
}
|
||||
|
||||
private function getCommitterInfo(DrydockRepositoryOperation $operation) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$committer_name = null;
|
||||
|
||||
$author_phid = $operation->getAuthorPHID();
|
||||
$object = id(new PhabricatorObjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($author_phid))
|
||||
->executeOne();
|
||||
|
||||
if ($object) {
|
||||
if ($object instanceof PhabricatorUser) {
|
||||
$committer_name = $object->getUsername();
|
||||
}
|
||||
}
|
||||
|
||||
if (!strlen($committer_name)) {
|
||||
$committer_name = pht('autocommitter');
|
||||
}
|
||||
|
||||
// TODO: Probably let users choose a VCS email address in settings. For
|
||||
// now just make something up so we don't leak anyone's stuff.
|
||||
|
||||
return array(
|
||||
'name' => $committer_name,
|
||||
'email' => 'autocommitter@example.com',
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
abstract class DrydockRepositoryOperationType extends Phobject {
|
||||
|
||||
private $viewer;
|
||||
|
||||
abstract public function applyOperation(
|
||||
DrydockRepositoryOperation $operation,
|
||||
DrydockInterface $interface);
|
||||
|
||||
abstract public function getOperationDescription(
|
||||
DrydockRepositoryOperation $operation,
|
||||
PhabricatorUser $viewer);
|
||||
|
||||
final public function setViewer(PhabricatorUser $viewer) {
|
||||
$this->viewer = $viewer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getViewer() {
|
||||
return $this->viewer;
|
||||
}
|
||||
|
||||
final public function getOperationConstant() {
|
||||
return $this->getPhobjectClassConstant('OPCONST', 32);
|
||||
}
|
||||
|
||||
final public static function getAllOperationTypes() {
|
||||
return id(new PhutilClassMapQuery())
|
||||
->setAncestorClass(__CLASS__)
|
||||
->setUniqueMethod('getOperationConstant')
|
||||
->execute();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
final class DrydockAuthorizationPHIDType extends PhabricatorPHIDType {
|
||||
|
||||
const TYPECONST = 'DRYA';
|
||||
|
||||
public function getTypeName() {
|
||||
return pht('Drydock Authorization');
|
||||
}
|
||||
|
||||
public function newObject() {
|
||||
return new DrydockAuthorization();
|
||||
}
|
||||
|
||||
protected function buildQueryForObjects(
|
||||
PhabricatorObjectQuery $query,
|
||||
array $phids) {
|
||||
|
||||
return id(new DrydockAuthorizationQuery())
|
||||
->withPHIDs($phids);
|
||||
}
|
||||
|
||||
public function loadHandles(
|
||||
PhabricatorHandleQuery $query,
|
||||
array $handles,
|
||||
array $objects) {
|
||||
|
||||
foreach ($handles as $phid => $handle) {
|
||||
$authorization = $objects[$phid];
|
||||
$id = $authorization->getID();
|
||||
|
||||
$handle->setName(pht('Drydock Authorization %d', $id));
|
||||
$handle->setURI("/drydock/authorization/{$id}/");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -8,6 +8,14 @@ final class DrydockBlueprintPHIDType extends PhabricatorPHIDType {
|
|||
return pht('Blueprint');
|
||||
}
|
||||
|
||||
public function getPHIDTypeApplicationClass() {
|
||||
return 'PhabricatorDrydockApplication';
|
||||
}
|
||||
|
||||
public function getTypeIcon() {
|
||||
return 'fa-map-o';
|
||||
}
|
||||
|
||||
public function newObject() {
|
||||
return new DrydockBlueprint();
|
||||
}
|
||||
|
@ -28,9 +36,12 @@ final class DrydockBlueprintPHIDType extends PhabricatorPHIDType {
|
|||
foreach ($handles as $phid => $handle) {
|
||||
$blueprint = $objects[$phid];
|
||||
$id = $blueprint->getID();
|
||||
$name = $blueprint->getBlueprintName();
|
||||
|
||||
$handle->setName($blueprint->getBlueprintName());
|
||||
$handle->setURI("/drydock/blueprint/{$id}/");
|
||||
$handle
|
||||
->setName($name)
|
||||
->setFullName(pht('Blueprint %d: %s', $id, $name))
|
||||
->setURI("/drydock/blueprint/{$id}/");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,14 @@ final class DrydockLeasePHIDType extends PhabricatorPHIDType {
|
|||
return pht('Drydock Lease');
|
||||
}
|
||||
|
||||
public function getPHIDTypeApplicationClass() {
|
||||
return 'PhabricatorDrydockApplication';
|
||||
}
|
||||
|
||||
public function getTypeIcon() {
|
||||
return 'fa-link';
|
||||
}
|
||||
|
||||
public function newObject() {
|
||||
return new DrydockLease();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
final class DrydockRepositoryOperationPHIDType extends PhabricatorPHIDType {
|
||||
|
||||
const TYPECONST = 'DRYO';
|
||||
|
||||
public function getTypeName() {
|
||||
return pht('Drydock Repository Operation');
|
||||
}
|
||||
|
||||
public function newObject() {
|
||||
return new DrydockRepositoryOperation();
|
||||
}
|
||||
|
||||
protected function buildQueryForObjects(
|
||||
PhabricatorObjectQuery $query,
|
||||
array $phids) {
|
||||
|
||||
return id(new DrydockRepositoryOperationQuery())
|
||||
->withPHIDs($phids);
|
||||
}
|
||||
|
||||
public function loadHandles(
|
||||
PhabricatorHandleQuery $query,
|
||||
array $handles,
|
||||
array $objects) {
|
||||
|
||||
foreach ($handles as $phid => $handle) {
|
||||
$operation = $objects[$phid];
|
||||
$id = $operation->getID();
|
||||
|
||||
$handle->setName(pht('Drydock Repository Operation %d', $id));
|
||||
$handle->setURI("/drydock/operation/{$id}/");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -8,6 +8,14 @@ final class DrydockResourcePHIDType extends PhabricatorPHIDType {
|
|||
return pht('Drydock Resource');
|
||||
}
|
||||
|
||||
public function getPHIDTypeApplicationClass() {
|
||||
return 'PhabricatorDrydockApplication';
|
||||
}
|
||||
|
||||
public function getTypeIcon() {
|
||||
return 'fa-map';
|
||||
}
|
||||
|
||||
public function newObject() {
|
||||
return new DrydockResource();
|
||||
}
|
||||
|
|
146
src/applications/drydock/query/DrydockAuthorizationQuery.php
Normal file
146
src/applications/drydock/query/DrydockAuthorizationQuery.php
Normal file
|
@ -0,0 +1,146 @@
|
|||
<?php
|
||||
|
||||
final class DrydockAuthorizationQuery extends DrydockQuery {
|
||||
|
||||
private $ids;
|
||||
private $phids;
|
||||
private $blueprintPHIDs;
|
||||
private $objectPHIDs;
|
||||
private $blueprintStates;
|
||||
private $objectStates;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withPHIDs(array $phids) {
|
||||
$this->phids = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withBlueprintPHIDs(array $phids) {
|
||||
$this->blueprintPHIDs = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withObjectPHIDs(array $phids) {
|
||||
$this->objectPHIDs = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withBlueprintStates(array $states) {
|
||||
$this->blueprintStates = $states;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withObjectStates(array $states) {
|
||||
$this->objectStates = $states;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function newResultObject() {
|
||||
return new DrydockAuthorization();
|
||||
}
|
||||
|
||||
protected function loadPage() {
|
||||
return $this->loadStandardPage($this->newResultObject());
|
||||
}
|
||||
|
||||
protected function willFilterPage(array $authorizations) {
|
||||
$blueprint_phids = mpull($authorizations, 'getBlueprintPHID');
|
||||
if ($blueprint_phids) {
|
||||
$blueprints = id(new DrydockBlueprintQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->setParentQuery($this)
|
||||
->withPHIDs($blueprint_phids)
|
||||
->execute();
|
||||
$blueprints = mpull($blueprints, null, 'getPHID');
|
||||
} else {
|
||||
$blueprints = array();
|
||||
}
|
||||
|
||||
foreach ($authorizations as $key => $authorization) {
|
||||
$blueprint = idx($blueprints, $authorization->getBlueprintPHID());
|
||||
if (!$blueprint) {
|
||||
$this->didRejectResult($authorization);
|
||||
unset($authorizations[$key]);
|
||||
continue;
|
||||
}
|
||||
$authorization->attachBlueprint($blueprint);
|
||||
}
|
||||
|
||||
$object_phids = mpull($authorizations, 'getObjectPHID');
|
||||
if ($object_phids) {
|
||||
$objects = id(new PhabricatorObjectQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->setParentQuery($this)
|
||||
->withPHIDs($object_phids)
|
||||
->execute();
|
||||
$objects = mpull($objects, null, 'getPHID');
|
||||
} else {
|
||||
$objects = array();
|
||||
}
|
||||
|
||||
foreach ($authorizations as $key => $authorization) {
|
||||
$object = idx($objects, $authorization->getObjectPHID());
|
||||
if (!$object) {
|
||||
$this->didRejectResult($authorization);
|
||||
unset($authorizations[$key]);
|
||||
continue;
|
||||
}
|
||||
$authorization->attachObject($object);
|
||||
}
|
||||
|
||||
return $authorizations;
|
||||
}
|
||||
|
||||
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$where = parent::buildWhereClauseParts($conn);
|
||||
|
||||
if ($this->ids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'id IN (%Ld)',
|
||||
$this->ids);
|
||||
}
|
||||
|
||||
if ($this->phids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'phid IN (%Ls)',
|
||||
$this->phids);
|
||||
}
|
||||
|
||||
if ($this->blueprintPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'blueprintPHID IN (%Ls)',
|
||||
$this->blueprintPHIDs);
|
||||
}
|
||||
|
||||
if ($this->objectPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'objectPHID IN (%Ls)',
|
||||
$this->objectPHIDs);
|
||||
}
|
||||
|
||||
if ($this->blueprintStates !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'blueprintAuthorizationState IN (%Ls)',
|
||||
$this->blueprintStates);
|
||||
}
|
||||
|
||||
if ($this->objectStates !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'objectAuthorizationState IN (%Ls)',
|
||||
$this->objectStates);
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
final class DrydockAuthorizationSearchEngine
|
||||
extends PhabricatorApplicationSearchEngine {
|
||||
|
||||
private $blueprint;
|
||||
|
||||
public function setBlueprint(DrydockBlueprint $blueprint) {
|
||||
$this->blueprint = $blueprint;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBlueprint() {
|
||||
return $this->blueprint;
|
||||
}
|
||||
|
||||
public function getResultTypeDescription() {
|
||||
return pht('Drydock Authorizations');
|
||||
}
|
||||
|
||||
public function getApplicationClassName() {
|
||||
return 'PhabricatorDrydockApplication';
|
||||
}
|
||||
|
||||
public function canUseInPanelContext() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function newQuery() {
|
||||
$query = new DrydockAuthorizationQuery();
|
||||
|
||||
$blueprint = $this->getBlueprint();
|
||||
$query->withBlueprintPHIDs(array($blueprint->getPHID()));
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function buildQueryFromParameters(array $map) {
|
||||
$query = $this->newQuery();
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function buildCustomSearchFields() {
|
||||
return array();
|
||||
}
|
||||
|
||||
protected function getURI($path) {
|
||||
$blueprint = $this->getBlueprint();
|
||||
$id = $blueprint->getID();
|
||||
return "/drydock/blueprint/{$id}/authorizations/".$path;
|
||||
}
|
||||
|
||||
protected function getBuiltinQueryNames() {
|
||||
return array(
|
||||
'all' => pht('All Authorizations'),
|
||||
);
|
||||
}
|
||||
|
||||
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 $authorizations,
|
||||
PhabricatorSavedQuery $query,
|
||||
array $handles) {
|
||||
|
||||
$list = id(new DrydockAuthorizationListView())
|
||||
->setUser($this->requireViewer())
|
||||
->setAuthorizations($authorizations);
|
||||
|
||||
$result = new PhabricatorApplicationSearchResultView();
|
||||
$result->setTable($list);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
|
@ -7,6 +7,7 @@ final class DrydockBlueprintQuery extends DrydockQuery {
|
|||
private $blueprintClasses;
|
||||
private $datasourceQuery;
|
||||
private $disabled;
|
||||
private $authorizedPHIDs;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
|
@ -33,10 +34,19 @@ final class DrydockBlueprintQuery extends DrydockQuery {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withAuthorizedPHIDs(array $phids) {
|
||||
$this->authorizedPHIDs = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function newResultObject() {
|
||||
return new DrydockBlueprint();
|
||||
}
|
||||
|
||||
protected function getPrimaryTableAlias() {
|
||||
return 'blueprint';
|
||||
}
|
||||
|
||||
protected function loadPage() {
|
||||
return $this->loadStandardPage($this->newResultObject());
|
||||
}
|
||||
|
@ -63,39 +73,66 @@ final class DrydockBlueprintQuery extends DrydockQuery {
|
|||
if ($this->ids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'id IN (%Ld)',
|
||||
'blueprint.id IN (%Ld)',
|
||||
$this->ids);
|
||||
}
|
||||
|
||||
if ($this->phids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'phid IN (%Ls)',
|
||||
'blueprint.phid IN (%Ls)',
|
||||
$this->phids);
|
||||
}
|
||||
|
||||
if ($this->datasourceQuery !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'blueprintName LIKE %>',
|
||||
'blueprint.blueprintName LIKE %>',
|
||||
$this->datasourceQuery);
|
||||
}
|
||||
|
||||
if ($this->blueprintClasses !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'className IN (%Ls)',
|
||||
'blueprint.className IN (%Ls)',
|
||||
$this->blueprintClasses);
|
||||
}
|
||||
|
||||
if ($this->disabled !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'isDisabled = %d',
|
||||
'blueprint.isDisabled = %d',
|
||||
(int)$this->disabled);
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
protected function shouldGroupQueryResultRows() {
|
||||
if ($this->authorizedPHIDs !== null) {
|
||||
return true;
|
||||
}
|
||||
return parent::shouldGroupQueryResultRows();
|
||||
}
|
||||
|
||||
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$joins = parent::buildJoinClauseParts($conn);
|
||||
|
||||
if ($this->authorizedPHIDs !== null) {
|
||||
$joins[] = qsprintf(
|
||||
$conn,
|
||||
'JOIN %T authorization
|
||||
ON authorization.blueprintPHID = blueprint.phid
|
||||
AND authorization.objectPHID IN (%Ls)
|
||||
AND authorization.objectAuthorizationState = %s
|
||||
AND authorization.blueprintAuthorizationState = %s',
|
||||
id(new DrydockAuthorization())->getTableName(),
|
||||
$this->authorizedPHIDs,
|
||||
DrydockAuthorization::OBJECTAUTH_ACTIVE,
|
||||
DrydockAuthorization::BLUEPRINTAUTH_AUTHORIZED);
|
||||
}
|
||||
|
||||
return $joins;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
|
||||
final class DrydockRepositoryOperationQuery extends DrydockQuery {
|
||||
|
||||
private $ids;
|
||||
private $phids;
|
||||
private $objectPHIDs;
|
||||
private $repositoryPHIDs;
|
||||
private $operationStates;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withPHIDs(array $phids) {
|
||||
$this->phids = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withObjectPHIDs(array $object_phids) {
|
||||
$this->objectPHIDs = $object_phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withRepositoryPHIDs(array $repository_phids) {
|
||||
$this->repositoryPHIDs = $repository_phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withOperationStates(array $states) {
|
||||
$this->operationStates = $states;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function newResultObject() {
|
||||
return new DrydockRepositoryOperation();
|
||||
}
|
||||
|
||||
protected function loadPage() {
|
||||
return $this->loadStandardPage($this->newResultObject());
|
||||
}
|
||||
|
||||
protected function willFilterPage(array $operations) {
|
||||
$implementations = DrydockRepositoryOperationType::getAllOperationTypes();
|
||||
|
||||
foreach ($operations as $key => $operation) {
|
||||
$impl = idx($implementations, $operation->getOperationType());
|
||||
if (!$impl) {
|
||||
$this->didRejectResult($operation);
|
||||
unset($operations[$key]);
|
||||
continue;
|
||||
}
|
||||
$impl = clone $impl;
|
||||
$operation->attachImplementation($impl);
|
||||
}
|
||||
|
||||
$repository_phids = mpull($operations, 'getRepositoryPHID');
|
||||
if ($repository_phids) {
|
||||
$repositories = id(new PhabricatorRepositoryQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->setParentQuery($this)
|
||||
->withPHIDs($repository_phids)
|
||||
->execute();
|
||||
$repositories = mpull($repositories, null, 'getPHID');
|
||||
} else {
|
||||
$repositories = array();
|
||||
}
|
||||
|
||||
foreach ($operations as $key => $operation) {
|
||||
$repository = idx($repositories, $operation->getRepositoryPHID());
|
||||
if (!$repository) {
|
||||
$this->didRejectResult($operation);
|
||||
unset($operations[$key]);
|
||||
continue;
|
||||
}
|
||||
$operation->attachRepository($repository);
|
||||
}
|
||||
|
||||
return $operations;
|
||||
}
|
||||
|
||||
protected function didFilterPage(array $operations) {
|
||||
$object_phids = mpull($operations, 'getObjectPHID');
|
||||
if ($object_phids) {
|
||||
$objects = id(new PhabricatorObjectQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->setParentQuery($this)
|
||||
->withPHIDs($object_phids)
|
||||
->execute();
|
||||
$objects = mpull($objects, null, 'getPHID');
|
||||
} else {
|
||||
$objects = array();
|
||||
}
|
||||
|
||||
foreach ($operations as $key => $operation) {
|
||||
$object = idx($objects, $operation->getObjectPHID());
|
||||
$operation->attachObject($object);
|
||||
}
|
||||
|
||||
return $operations;
|
||||
}
|
||||
|
||||
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$where = parent::buildWhereClauseParts($conn);
|
||||
|
||||
if ($this->ids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'id IN (%Ld)',
|
||||
$this->ids);
|
||||
}
|
||||
|
||||
if ($this->phids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'phid IN (%Ls)',
|
||||
$this->phids);
|
||||
}
|
||||
|
||||
if ($this->objectPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'objectPHID IN (%Ls)',
|
||||
$this->objectPHIDs);
|
||||
}
|
||||
|
||||
if ($this->repositoryPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'repositoryPHID IN (%Ls)',
|
||||
$this->repositoryPHIDs);
|
||||
}
|
||||
|
||||
if ($this->operationStates !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'operationState IN (%Ls)',
|
||||
$this->operationStates);
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
final class DrydockRepositoryOperationSearchEngine
|
||||
extends PhabricatorApplicationSearchEngine {
|
||||
|
||||
public function getResultTypeDescription() {
|
||||
return pht('Drydock Repository Operations');
|
||||
}
|
||||
|
||||
public function getApplicationClassName() {
|
||||
return 'PhabricatorDrydockApplication';
|
||||
}
|
||||
|
||||
public function newQuery() {
|
||||
return id(new DrydockRepositoryOperationQuery());
|
||||
}
|
||||
|
||||
protected function buildQueryFromParameters(array $map) {
|
||||
$query = $this->newQuery();
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function buildCustomSearchFields() {
|
||||
return array(
|
||||
);
|
||||
}
|
||||
|
||||
protected function getURI($path) {
|
||||
return '/drydock/operation/'.$path;
|
||||
}
|
||||
|
||||
protected function getBuiltinQueryNames() {
|
||||
return array(
|
||||
'all' => pht('All Operations'),
|
||||
);
|
||||
}
|
||||
|
||||
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 $operations,
|
||||
PhabricatorSavedQuery $query,
|
||||
array $handles) {
|
||||
assert_instances_of($operations, 'DrydockRepositoryOperation');
|
||||
|
||||
$viewer = $this->requireViewer();
|
||||
|
||||
$view = new PHUIObjectItemListView();
|
||||
foreach ($operations as $operation) {
|
||||
$id = $operation->getID();
|
||||
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setHeader($operation->getOperationDescription($viewer))
|
||||
->setHref($this->getApplicationURI("operation/{$id}/"))
|
||||
->setObjectName(pht('Repository Operation %d', $id));
|
||||
|
||||
$state = $operation->getOperationState();
|
||||
|
||||
$icon = DrydockRepositoryOperation::getOperationStateIcon($state);
|
||||
$name = DrydockRepositoryOperation::getOperationStateName($state);
|
||||
|
||||
$item->addIcon($icon, $name);
|
||||
$item->addByline(
|
||||
array(
|
||||
pht('Via:'),
|
||||
' ',
|
||||
$viewer->renderHandle($operation->getAuthorPHID()),
|
||||
));
|
||||
|
||||
$item->addAttribute(
|
||||
$viewer->renderHandle(
|
||||
$operation->getObjectPHID()));
|
||||
|
||||
$item->addAttribute(
|
||||
$viewer->renderHandle(
|
||||
$operation->getRepositoryPHID()));
|
||||
|
||||
$view->addItem($item);
|
||||
}
|
||||
|
||||
$result = id(new PhabricatorApplicationSearchResultView())
|
||||
->setObjectList($view)
|
||||
->setNoDataString(pht('No matching operations.'));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
202
src/applications/drydock/storage/DrydockAuthorization.php
Normal file
202
src/applications/drydock/storage/DrydockAuthorization.php
Normal file
|
@ -0,0 +1,202 @@
|
|||
<?php
|
||||
|
||||
final class DrydockAuthorization extends DrydockDAO
|
||||
implements
|
||||
PhabricatorPolicyInterface {
|
||||
|
||||
const OBJECTAUTH_ACTIVE = 'active';
|
||||
const OBJECTAUTH_INACTIVE = 'inactive';
|
||||
|
||||
const BLUEPRINTAUTH_REQUESTED = 'requested';
|
||||
const BLUEPRINTAUTH_AUTHORIZED = 'authorized';
|
||||
const BLUEPRINTAUTH_DECLINED = 'declined';
|
||||
|
||||
protected $blueprintPHID;
|
||||
protected $blueprintAuthorizationState;
|
||||
protected $objectPHID;
|
||||
protected $objectAuthorizationState;
|
||||
|
||||
private $blueprint = self::ATTACHABLE;
|
||||
private $object = self::ATTACHABLE;
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'blueprintAuthorizationState' => 'text32',
|
||||
'objectAuthorizationState' => 'text32',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_unique' => array(
|
||||
'columns' => array('objectPHID', 'blueprintPHID'),
|
||||
'unique' => true,
|
||||
),
|
||||
'key_blueprint' => array(
|
||||
'columns' => array('blueprintPHID', 'blueprintAuthorizationState'),
|
||||
),
|
||||
'key_object' => array(
|
||||
'columns' => array('objectPHID', 'objectAuthorizationState'),
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
public function generatePHID() {
|
||||
return PhabricatorPHID::generateNewPHID(
|
||||
DrydockAuthorizationPHIDType::TYPECONST);
|
||||
}
|
||||
|
||||
public function attachBlueprint(DrydockBlueprint $blueprint) {
|
||||
$this->blueprint = $blueprint;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBlueprint() {
|
||||
return $this->assertAttached($this->blueprint);
|
||||
}
|
||||
|
||||
public function attachObject($object) {
|
||||
$this->object = $object;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getObject() {
|
||||
return $this->assertAttached($this->object);
|
||||
}
|
||||
|
||||
public static function getBlueprintStateIcon($state) {
|
||||
$map = array(
|
||||
self::BLUEPRINTAUTH_REQUESTED => 'fa-exclamation-circle indigo',
|
||||
self::BLUEPRINTAUTH_AUTHORIZED => 'fa-check-circle green',
|
||||
self::BLUEPRINTAUTH_DECLINED => 'fa-times red',
|
||||
);
|
||||
|
||||
return idx($map, $state, null);
|
||||
}
|
||||
|
||||
public static function getBlueprintStateName($state) {
|
||||
$map = array(
|
||||
self::BLUEPRINTAUTH_REQUESTED => pht('Requested'),
|
||||
self::BLUEPRINTAUTH_AUTHORIZED => pht('Authorized'),
|
||||
self::BLUEPRINTAUTH_DECLINED => pht('Declined'),
|
||||
);
|
||||
|
||||
return idx($map, $state, pht('<Unknown: %s>', $state));
|
||||
}
|
||||
|
||||
public static function getObjectStateName($state) {
|
||||
$map = array(
|
||||
self::OBJECTAUTH_ACTIVE => pht('Active'),
|
||||
self::OBJECTAUTH_INACTIVE => pht('Inactive'),
|
||||
);
|
||||
|
||||
return idx($map, $state, pht('<Unknown: %s>', $state));
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply external authorization effects after a user chagnes the value of a
|
||||
* blueprint selector control an object.
|
||||
*
|
||||
* @param PhabricatorUser User applying the change.
|
||||
* @param phid Object PHID change is being applied to.
|
||||
* @param list<phid> Old blueprint PHIDs.
|
||||
* @param list<phid> New blueprint PHIDs.
|
||||
* @return void
|
||||
*/
|
||||
public static function applyAuthorizationChanges(
|
||||
PhabricatorUser $viewer,
|
||||
$object_phid,
|
||||
array $old,
|
||||
array $new) {
|
||||
|
||||
$old_phids = array_fuse($old);
|
||||
$new_phids = array_fuse($new);
|
||||
|
||||
$rem_phids = array_diff_key($old_phids, $new_phids);
|
||||
$add_phids = array_diff_key($new_phids, $old_phids);
|
||||
|
||||
$altered_phids = $rem_phids + $add_phids;
|
||||
|
||||
if (!$altered_phids) {
|
||||
return;
|
||||
}
|
||||
|
||||
$authorizations = id(new DrydockAuthorizationQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withObjectPHIDs(array($object_phid))
|
||||
->withBlueprintPHIDs($altered_phids)
|
||||
->execute();
|
||||
$authorizations = mpull($authorizations, null, 'getBlueprintPHID');
|
||||
|
||||
$state_active = self::OBJECTAUTH_ACTIVE;
|
||||
$state_inactive = self::OBJECTAUTH_INACTIVE;
|
||||
|
||||
$state_requested = self::BLUEPRINTAUTH_REQUESTED;
|
||||
|
||||
// Disable the object side of the authorization for any existing
|
||||
// authorizations.
|
||||
foreach ($rem_phids as $rem_phid) {
|
||||
$authorization = idx($authorizations, $rem_phid);
|
||||
if (!$authorization) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$authorization
|
||||
->setObjectAuthorizationState($state_inactive)
|
||||
->save();
|
||||
}
|
||||
|
||||
// For new authorizations, either add them or reactivate them depending
|
||||
// on the current state.
|
||||
foreach ($add_phids as $add_phid) {
|
||||
$needs_update = false;
|
||||
|
||||
$authorization = idx($authorizations, $add_phid);
|
||||
if (!$authorization) {
|
||||
$authorization = id(new DrydockAuthorization())
|
||||
->setObjectPHID($object_phid)
|
||||
->setObjectAuthorizationState($state_active)
|
||||
->setBlueprintPHID($add_phid)
|
||||
->setBlueprintAuthorizationState($state_requested);
|
||||
|
||||
$needs_update = true;
|
||||
} else {
|
||||
$current_state = $authorization->getObjectAuthorizationState();
|
||||
if ($current_state != $state_active) {
|
||||
$authorization->setObjectAuthorizationState($state_active);
|
||||
$needs_update = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($needs_update) {
|
||||
$authorization->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
||||
public function getCapabilities() {
|
||||
return array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
);
|
||||
}
|
||||
|
||||
public function getPolicy($capability) {
|
||||
return $this->getBlueprint()->getPolicy($capability);
|
||||
}
|
||||
|
||||
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||
return $this->getBlueprint()->hasAutomaticCapability($capability, $viewer);
|
||||
}
|
||||
|
||||
public function describeAutomaticCapability($capability) {
|
||||
return pht(
|
||||
'An authorization inherits the policies of the blueprint it '.
|
||||
'authorizes access to.');
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -7,6 +7,7 @@ final class DrydockLease extends DrydockDAO
|
|||
protected $resourceType;
|
||||
protected $until;
|
||||
protected $ownerPHID;
|
||||
protected $authorizingPHID;
|
||||
protected $attributes = array();
|
||||
protected $status = DrydockLeaseStatus::STATUS_PENDING;
|
||||
|
||||
|
@ -141,6 +142,25 @@ final class DrydockLease extends DrydockDAO
|
|||
pht('Only new leases may be queued for activation!'));
|
||||
}
|
||||
|
||||
if (!$this->getAuthorizingPHID()) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Trying to queue a lease for activation without an authorizing '.
|
||||
'object. Use "%s" to specify the PHID of the authorizing object. '.
|
||||
'The authorizing object must be approved to use the allowed '.
|
||||
'blueprints.',
|
||||
'setAuthorizingPHID()'));
|
||||
}
|
||||
|
||||
if (!$this->getAllowedBlueprintPHIDs()) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Trying to queue a lease for activation without any allowed '.
|
||||
'Blueprints. Use "%s" to specify allowed blueprints. The '.
|
||||
'authorizing object must be approved to use the allowed blueprints.',
|
||||
'setAllowedBlueprintPHIDs()'));
|
||||
}
|
||||
|
||||
$this
|
||||
->setStatus(DrydockLeaseStatus::STATUS_PENDING)
|
||||
->save();
|
||||
|
@ -376,6 +396,15 @@ final class DrydockLease extends DrydockDAO
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setAllowedBlueprintPHIDs(array $phids) {
|
||||
$this->setAttribute('internal.blueprintPHIDs', $phids);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAllowedBlueprintPHIDs() {
|
||||
return $this->getAttribute('internal.blueprintPHIDs', array());
|
||||
}
|
||||
|
||||
private function didActivate() {
|
||||
$viewer = PhabricatorUser::getOmnipotentUser();
|
||||
$need_update = false;
|
||||
|
|
170
src/applications/drydock/storage/DrydockRepositoryOperation.php
Normal file
170
src/applications/drydock/storage/DrydockRepositoryOperation.php
Normal file
|
@ -0,0 +1,170 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Represents a request to perform a repository operation like a merge or
|
||||
* cherry-pick.
|
||||
*/
|
||||
final class DrydockRepositoryOperation extends DrydockDAO
|
||||
implements
|
||||
PhabricatorPolicyInterface {
|
||||
|
||||
const STATE_WAIT = 'wait';
|
||||
const STATE_WORK = 'work';
|
||||
const STATE_DONE = 'done';
|
||||
const STATE_FAIL = 'fail';
|
||||
|
||||
protected $authorPHID;
|
||||
protected $objectPHID;
|
||||
protected $repositoryPHID;
|
||||
protected $repositoryTarget;
|
||||
protected $operationType;
|
||||
protected $operationState;
|
||||
protected $properties = array();
|
||||
|
||||
private $repository = self::ATTACHABLE;
|
||||
private $object = self::ATTACHABLE;
|
||||
private $implementation = self::ATTACHABLE;
|
||||
|
||||
public static function initializeNewOperation(
|
||||
DrydockRepositoryOperationType $op) {
|
||||
|
||||
return id(new DrydockRepositoryOperation())
|
||||
->setOperationState(self::STATE_WAIT)
|
||||
->setOperationType($op->getOperationConstant());
|
||||
}
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
self::CONFIG_SERIALIZATION => array(
|
||||
'properties' => self::SERIALIZATION_JSON,
|
||||
),
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'repositoryTarget' => 'bytes',
|
||||
'operationType' => 'text32',
|
||||
'operationState' => 'text32',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_object' => array(
|
||||
'columns' => array('objectPHID'),
|
||||
),
|
||||
'key_repository' => array(
|
||||
'columns' => array('repositoryPHID', 'operationState'),
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
public function generatePHID() {
|
||||
return PhabricatorPHID::generateNewPHID(
|
||||
DrydockRepositoryOperationPHIDType::TYPECONST);
|
||||
}
|
||||
|
||||
public function attachRepository(PhabricatorRepository $repository) {
|
||||
$this->repository = $repository;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRepository() {
|
||||
return $this->assertAttached($this->repository);
|
||||
}
|
||||
|
||||
public function attachObject($object) {
|
||||
$this->object = $object;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getObject() {
|
||||
return $this->assertAttached($this->object);
|
||||
}
|
||||
|
||||
public function attachImplementation(DrydockRepositoryOperationType $impl) {
|
||||
$this->implementation = $impl;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getImplementation() {
|
||||
return $this->implementation;
|
||||
}
|
||||
|
||||
public function getProperty($key, $default = null) {
|
||||
return idx($this->properties, $key, $default);
|
||||
}
|
||||
|
||||
public function setProperty($key, $value) {
|
||||
$this->properties[$key] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public static function getOperationStateIcon($state) {
|
||||
$map = array(
|
||||
self::STATE_WAIT => 'fa-clock-o',
|
||||
self::STATE_WORK => 'fa-refresh blue',
|
||||
self::STATE_DONE => 'fa-check green',
|
||||
self::STATE_FAIL => 'fa-times red',
|
||||
);
|
||||
|
||||
return idx($map, $state, null);
|
||||
}
|
||||
|
||||
public static function getOperationStateName($state) {
|
||||
$map = array(
|
||||
self::STATE_WAIT => pht('Waiting'),
|
||||
self::STATE_WORK => pht('Working'),
|
||||
self::STATE_DONE => pht('Done'),
|
||||
self::STATE_FAIL => pht('Failed'),
|
||||
);
|
||||
|
||||
return idx($map, $state, pht('<Unknown: %s>', $state));
|
||||
}
|
||||
|
||||
public function scheduleUpdate() {
|
||||
PhabricatorWorker::scheduleTask(
|
||||
'DrydockRepositoryOperationUpdateWorker',
|
||||
array(
|
||||
'operationPHID' => $this->getPHID(),
|
||||
),
|
||||
array(
|
||||
'objectPHID' => $this->getPHID(),
|
||||
'priority' => PhabricatorWorker::PRIORITY_ALERTS,
|
||||
));
|
||||
}
|
||||
|
||||
public function applyOperation(DrydockInterface $interface) {
|
||||
return $this->getImplementation()->applyOperation(
|
||||
$this,
|
||||
$interface);
|
||||
}
|
||||
|
||||
public function getOperationDescription(PhabricatorUser $viewer) {
|
||||
return $this->getImplementation()->getOperationDescription(
|
||||
$this,
|
||||
$viewer);
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
||||
public function getCapabilities() {
|
||||
return array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
);
|
||||
}
|
||||
|
||||
public function getPolicy($capability) {
|
||||
return $this->getRepository()->getPolicy($capability);
|
||||
}
|
||||
|
||||
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||
return $this->getRepository()->hasAutomaticCapability($capability, $viewer);
|
||||
}
|
||||
|
||||
public function describeAutomaticCapability($capability) {
|
||||
return pht(
|
||||
'A repository operation inherits the policies of the repository it '.
|
||||
'affects.');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
final class DrydockAuthorizationListView extends AphrontView {
|
||||
|
||||
private $authorizations;
|
||||
private $noDataString;
|
||||
|
||||
public function setAuthorizations(array $authorizations) {
|
||||
assert_instances_of($authorizations, 'DrydockAuthorization');
|
||||
$this->authorizations = $authorizations;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setNoDataString($string) {
|
||||
$this->noDataString = $string;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNoDataString() {
|
||||
return $this->noDataString;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$viewer = $this->getUser();
|
||||
|
||||
$authorizations = $this->authorizations;
|
||||
|
||||
$view = new PHUIObjectItemListView();
|
||||
|
||||
$nodata = $this->getNoDataString();
|
||||
if ($nodata) {
|
||||
$view->setNoDataString($nodata);
|
||||
}
|
||||
|
||||
$handles = $viewer->loadHandles(mpull($authorizations, 'getObjectPHID'));
|
||||
|
||||
foreach ($authorizations as $authorization) {
|
||||
$id = $authorization->getID();
|
||||
$object_phid = $authorization->getObjectPHID();
|
||||
$handle = $handles[$object_phid];
|
||||
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setHref("/drydock/authorization/{$id}/")
|
||||
->setObjectName(pht('Authorization %d', $id))
|
||||
->setHeader($handle->getFullName());
|
||||
|
||||
$item->addAttribute($handle->getTypeName());
|
||||
|
||||
$object_state = $authorization->getObjectAuthorizationState();
|
||||
$item->addAttribute(
|
||||
DrydockAuthorization::getObjectStateName($object_state));
|
||||
|
||||
$state = $authorization->getBlueprintAuthorizationState();
|
||||
$icon = DrydockAuthorization::getBlueprintStateIcon($state);
|
||||
$name = DrydockAuthorization::getBlueprintStateName($state);
|
||||
|
||||
$item->setStatusIcon($icon, $name);
|
||||
|
||||
$view->addItem($item);
|
||||
}
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
final class DrydockObjectAuthorizationView extends AphrontView {
|
||||
|
||||
private $objectPHID;
|
||||
private $blueprintPHIDs;
|
||||
|
||||
public function setObjectPHID($object_phid) {
|
||||
$this->objectPHID = $object_phid;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getObjectPHID() {
|
||||
return $this->objectPHID;
|
||||
}
|
||||
|
||||
public function setBlueprintPHIDs(array $blueprint_phids) {
|
||||
$this->blueprintPHIDs = $blueprint_phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBlueprintPHIDs() {
|
||||
return $this->blueprintPHIDs;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$viewer = $this->getUser();
|
||||
$blueprint_phids = $this->getBlueprintPHIDs();
|
||||
$object_phid = $this->getObjectPHID();
|
||||
|
||||
// NOTE: We're intentionally letting you see the authorization state on
|
||||
// blueprints you can't see because this has a tremendous potential to
|
||||
// be extremely confusing otherwise. You still can't see the blueprints
|
||||
// themselves, but you can know if the object is authorized on something.
|
||||
|
||||
if ($blueprint_phids) {
|
||||
$handles = $viewer->loadHandles($blueprint_phids);
|
||||
|
||||
$authorizations = id(new DrydockAuthorizationQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withObjectPHIDs(array($object_phid))
|
||||
->withBlueprintPHIDs($blueprint_phids)
|
||||
->execute();
|
||||
$authorizations = mpull($authorizations, null, 'getBlueprintPHID');
|
||||
} else {
|
||||
$handles = array();
|
||||
$authorizations = array();
|
||||
}
|
||||
|
||||
$items = array();
|
||||
foreach ($blueprint_phids as $phid) {
|
||||
$authorization = idx($authorizations, $phid);
|
||||
if (!$authorization) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$handle = $handles[$phid];
|
||||
|
||||
$item = id(new PHUIStatusItemView())
|
||||
->setTarget($handle->renderLink());
|
||||
|
||||
$state = $authorization->getBlueprintAuthorizationState();
|
||||
$item->setIcon(
|
||||
DrydockAuthorization::getBlueprintStateIcon($state),
|
||||
null,
|
||||
DrydockAuthorization::getBlueprintStateName($state));
|
||||
|
||||
$items[] = $item;
|
||||
}
|
||||
|
||||
$status = new PHUIStatusListView();
|
||||
foreach ($items as $item) {
|
||||
$status->addItem($item);
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
}
|
|
@ -211,10 +211,19 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker {
|
|||
$exceptions);
|
||||
}
|
||||
|
||||
$resources = $this->removeUnacquirableResources($resources, $lease);
|
||||
if (!$resources) {
|
||||
// If we make it here, we just built a resource but aren't allowed
|
||||
// to acquire it. We expect this during routine operation if the
|
||||
// resource prevents acquisition until it activates. Yield and wait
|
||||
// for activation.
|
||||
throw new PhabricatorWorkerYieldException(15);
|
||||
}
|
||||
|
||||
// NOTE: We have not acquired the lease yet, so it is possible that the
|
||||
// resource we just built will be snatched up by some other lease before
|
||||
// we can. This is not problematic: we'll retry a little later and should
|
||||
// suceed eventually.
|
||||
// we can acquire it. This is not problematic: we'll retry a little later
|
||||
// and should suceed eventually.
|
||||
}
|
||||
|
||||
$resources = $this->rankResources($resources, $lease);
|
||||
|
@ -300,11 +309,46 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker {
|
|||
return array();
|
||||
}
|
||||
|
||||
$blueprints = id(new DrydockBlueprintQuery())
|
||||
$query = id(new DrydockBlueprintQuery())
|
||||
->setViewer($viewer)
|
||||
->withBlueprintClasses(array_keys($impls))
|
||||
->withDisabled(false)
|
||||
->execute();
|
||||
->withDisabled(false);
|
||||
|
||||
$blueprint_phids = $lease->getAllowedBlueprintPHIDs();
|
||||
if (!$blueprint_phids) {
|
||||
$lease->logEvent(DrydockLeaseNoBlueprintsLogType::LOGCONST);
|
||||
return array();
|
||||
}
|
||||
|
||||
// The Drydock application itself is allowed to authorize anything. This
|
||||
// is primarily used for leases generated by CLI administrative tools.
|
||||
$drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
|
||||
|
||||
$authorizing_phid = $lease->getAuthorizingPHID();
|
||||
if ($authorizing_phid != $drydock_phid) {
|
||||
$blueprints = id(clone $query)
|
||||
->withAuthorizedPHIDs(array($authorizing_phid))
|
||||
->execute();
|
||||
if (!$blueprints) {
|
||||
// If we didn't hit any blueprints, check if this is an authorization
|
||||
// problem: re-execute the query without the authorization constraint.
|
||||
// If the second query hits blueprints, the overall configuration is
|
||||
// fine but this is an authorization problem. If the second query also
|
||||
// comes up blank, this is some other kind of configuration issue so
|
||||
// we fall through to the default pathway.
|
||||
$all_blueprints = $query->execute();
|
||||
if ($all_blueprints) {
|
||||
$lease->logEvent(
|
||||
DrydockLeaseNoAuthorizationsLogType::LOGCONST,
|
||||
array(
|
||||
'authorizingPHID' => $authorizing_phid,
|
||||
));
|
||||
return array();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$blueprints = $query->execute();
|
||||
}
|
||||
|
||||
$keep = array();
|
||||
foreach ($blueprints as $key => $blueprint) {
|
||||
|
@ -347,6 +391,22 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker {
|
|||
))
|
||||
->execute();
|
||||
|
||||
return $this->removeUnacquirableResources($resources, $lease);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove resources which can not be acquired by a given lease from a list.
|
||||
*
|
||||
* @param list<DrydockResource> Candidate resources.
|
||||
* @param DrydockLease Acquiring lease.
|
||||
* @return list<DrydockResource> Resources which the lease may be able to
|
||||
* acquire.
|
||||
* @task allocator
|
||||
*/
|
||||
private function removeUnacquirableResources(
|
||||
array $resources,
|
||||
DrydockLease $lease) {
|
||||
$keep = array();
|
||||
foreach ($resources as $key => $resource) {
|
||||
$blueprint = $resource->getBlueprint();
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
<?php
|
||||
|
||||
final class DrydockRepositoryOperationUpdateWorker
|
||||
extends DrydockWorker {
|
||||
|
||||
protected function doWork() {
|
||||
$operation_phid = $this->getTaskDataValue('operationPHID');
|
||||
|
||||
$hash = PhabricatorHash::digestForIndex($operation_phid);
|
||||
$lock_key = 'drydock.operation:'.$hash;
|
||||
|
||||
$lock = PhabricatorGlobalLock::newLock($lock_key)
|
||||
->lock(1);
|
||||
|
||||
try {
|
||||
$operation = $this->loadOperation($operation_phid);
|
||||
$this->handleUpdate($operation);
|
||||
} catch (Exception $ex) {
|
||||
$lock->unlock();
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
$lock->unlock();
|
||||
}
|
||||
|
||||
|
||||
private function handleUpdate(DrydockRepositoryOperation $operation) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$operation_state = $operation->getOperationState();
|
||||
|
||||
switch ($operation_state) {
|
||||
case DrydockRepositoryOperation::STATE_WAIT:
|
||||
$operation
|
||||
->setOperationState(DrydockRepositoryOperation::STATE_WORK)
|
||||
->save();
|
||||
break;
|
||||
case DrydockRepositoryOperation::STATE_WORK:
|
||||
break;
|
||||
case DrydockRepositoryOperation::STATE_DONE:
|
||||
case DrydockRepositoryOperation::STATE_FAIL:
|
||||
// No more processing for these requests.
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: We should probably check for other running operations with lower
|
||||
// IDs and the same repository target and yield to them here? That is,
|
||||
// enforce sequential evaluation of operations against the same target so
|
||||
// that if you land "A" and then land "B", we always finish "A" first.
|
||||
// For now, just let stuff happen in any order. We can't lease until
|
||||
// we know we're good to move forward because we might deadlock if we do:
|
||||
// we're waiting for another operation to complete, and that operation is
|
||||
// waiting for a lease we're holding.
|
||||
|
||||
try {
|
||||
$lease = $this->loadWorkingCopyLease($operation);
|
||||
|
||||
$interface = $lease->getInterface(
|
||||
DrydockCommandInterface::INTERFACE_TYPE);
|
||||
|
||||
// No matter what happens here, destroy the lease away once we're done.
|
||||
$lease->releaseOnDestruction(true);
|
||||
|
||||
$operation->getImplementation()
|
||||
->setViewer($viewer);
|
||||
|
||||
$operation->applyOperation($interface);
|
||||
|
||||
} catch (PhabricatorWorkerYieldException $ex) {
|
||||
throw $ex;
|
||||
} catch (Exception $ex) {
|
||||
$operation
|
||||
->setOperationState(DrydockRepositoryOperation::STATE_FAIL)
|
||||
->save();
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
$operation
|
||||
->setOperationState(DrydockRepositoryOperation::STATE_DONE)
|
||||
->save();
|
||||
|
||||
// TODO: Once we have sequencing, we could awaken the next operation
|
||||
// against this target after finishing or failing.
|
||||
}
|
||||
|
||||
private function loadWorkingCopyLease(
|
||||
DrydockRepositoryOperation $operation) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
// TODO: This is very similar to leasing in Harbormaster, maybe we can
|
||||
// share some of the logic?
|
||||
|
||||
$lease_phid = $operation->getProperty('exec.leasePHID');
|
||||
if ($lease_phid) {
|
||||
$lease = id(new DrydockLeaseQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($lease_phid))
|
||||
->executeOne();
|
||||
if (!$lease) {
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht(
|
||||
'Lease "%s" could not be loaded.',
|
||||
$lease_phid));
|
||||
}
|
||||
} else {
|
||||
$working_copy_type = id(new DrydockWorkingCopyBlueprintImplementation())
|
||||
->getType();
|
||||
|
||||
$repository = $operation->getRepository();
|
||||
|
||||
$allowed_phids = $repository->getAutomationBlueprintPHIDs();
|
||||
$authorizing_phid = $repository->getPHID();
|
||||
|
||||
$lease = DrydockLease::initializeNewLease()
|
||||
->setResourceType($working_copy_type)
|
||||
->setOwnerPHID($operation->getPHID())
|
||||
->setAuthorizingPHID($authorizing_phid)
|
||||
->setAllowedBlueprintPHIDs($allowed_phids);
|
||||
|
||||
$map = $this->buildRepositoryMap($operation);
|
||||
|
||||
$lease->setAttribute('repositories.map', $map);
|
||||
|
||||
$task_id = $this->getCurrentWorkerTaskID();
|
||||
if ($task_id) {
|
||||
$lease->setAwakenTaskIDs(array($task_id));
|
||||
}
|
||||
|
||||
$operation
|
||||
->setProperty('exec.leasePHID', $lease->getPHID())
|
||||
->save();
|
||||
|
||||
$lease->queueForActivation();
|
||||
}
|
||||
|
||||
if ($lease->isActivating()) {
|
||||
throw new PhabricatorWorkerYieldException(15);
|
||||
}
|
||||
|
||||
if (!$lease->isActive()) {
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht(
|
||||
'Lease "%s" never activated.',
|
||||
$lease->getPHID()));
|
||||
}
|
||||
|
||||
return $lease;
|
||||
}
|
||||
|
||||
private function buildRepositoryMap(DrydockRepositoryOperation $operation) {
|
||||
$repository = $operation->getRepository();
|
||||
|
||||
$target = $operation->getRepositoryTarget();
|
||||
list($type, $name) = explode(':', $target, 2);
|
||||
switch ($type) {
|
||||
case 'branch':
|
||||
$spec = array(
|
||||
'branch' => $name,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Unknown repository operation target type "%s" (in target "%s").',
|
||||
$type,
|
||||
$target));
|
||||
}
|
||||
|
||||
$map = array();
|
||||
$map[$repository->getCloneName()] = array(
|
||||
'phid' => $repository->getPHID(),
|
||||
'default' => true,
|
||||
) + $spec;
|
||||
|
||||
return $map;
|
||||
}
|
||||
}
|
|
@ -36,6 +36,21 @@ abstract class DrydockWorker extends PhabricatorWorker {
|
|||
return $resource;
|
||||
}
|
||||
|
||||
protected function loadOperation($operation_phid) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$operation = id(new DrydockRepositoryOperationQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($operation_phid))
|
||||
->executeOne();
|
||||
if (!$operation) {
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht('No such operation "%s"!', $operation_phid));
|
||||
}
|
||||
|
||||
return $operation;
|
||||
}
|
||||
|
||||
protected function loadCommands($target_phid) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
|
|
|
@ -3,24 +3,15 @@
|
|||
final class HarbormasterBuildActionController
|
||||
extends HarbormasterController {
|
||||
|
||||
private $id;
|
||||
private $action;
|
||||
private $via;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->id = $data['id'];
|
||||
$this->action = $data['action'];
|
||||
$this->via = idx($data, 'via');
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
$command = $this->action;
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
$action = $request->getURIData('action');
|
||||
$via = $request->getURIData('via');
|
||||
|
||||
$build = id(new HarbormasterBuildQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($this->id))
|
||||
->withIDs(array($id))
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
|
@ -31,7 +22,7 @@ final class HarbormasterBuildActionController
|
|||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
switch ($command) {
|
||||
switch ($action) {
|
||||
case HarbormasterBuildCommand::COMMAND_RESTART:
|
||||
$can_issue = $build->canRestartBuild();
|
||||
break;
|
||||
|
@ -48,7 +39,7 @@ final class HarbormasterBuildActionController
|
|||
return new Aphront400Response();
|
||||
}
|
||||
|
||||
switch ($this->via) {
|
||||
switch ($via) {
|
||||
case 'buildable':
|
||||
$return_uri = '/'.$build->getBuildable()->getMonogram();
|
||||
break;
|
||||
|
@ -66,14 +57,14 @@ final class HarbormasterBuildActionController
|
|||
|
||||
$xaction = id(new HarbormasterBuildTransaction())
|
||||
->setTransactionType(HarbormasterBuildTransaction::TYPE_COMMAND)
|
||||
->setNewValue($command);
|
||||
->setNewValue($action);
|
||||
|
||||
$editor->applyTransactions($build, array($xaction));
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($return_uri);
|
||||
}
|
||||
|
||||
switch ($command) {
|
||||
switch ($action) {
|
||||
case HarbormasterBuildCommand::COMMAND_RESTART:
|
||||
if ($can_issue) {
|
||||
$title = pht('Really restart build?');
|
||||
|
|
|
@ -3,22 +3,14 @@
|
|||
final class HarbormasterBuildableActionController
|
||||
extends HarbormasterController {
|
||||
|
||||
private $id;
|
||||
private $action;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->id = $data['id'];
|
||||
$this->action = $data['action'];
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
$command = $this->action;
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
$action = $request->getURIData('action');
|
||||
|
||||
$buildable = id(new HarbormasterBuildableQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($this->id))
|
||||
->withIDs(array($id))
|
||||
->needBuilds(true)
|
||||
->requireCapabilities(
|
||||
array(
|
||||
|
@ -33,7 +25,7 @@ final class HarbormasterBuildableActionController
|
|||
$issuable = array();
|
||||
|
||||
foreach ($buildable->getBuilds() as $build) {
|
||||
switch ($command) {
|
||||
switch ($action) {
|
||||
case HarbormasterBuildCommand::COMMAND_RESTART:
|
||||
if ($build->canRestartBuild()) {
|
||||
$issuable[] = $build;
|
||||
|
@ -69,7 +61,7 @@ final class HarbormasterBuildableActionController
|
|||
|
||||
$xaction = id(new HarbormasterBuildableTransaction())
|
||||
->setTransactionType(HarbormasterBuildableTransaction::TYPE_COMMAND)
|
||||
->setNewValue($command);
|
||||
->setNewValue($action);
|
||||
|
||||
$editor->applyTransactions($buildable, array($xaction));
|
||||
|
||||
|
@ -82,14 +74,14 @@ final class HarbormasterBuildableActionController
|
|||
foreach ($issuable as $build) {
|
||||
$xaction = id(new HarbormasterBuildTransaction())
|
||||
->setTransactionType(HarbormasterBuildTransaction::TYPE_COMMAND)
|
||||
->setNewValue($command);
|
||||
->setNewValue($action);
|
||||
$build_editor->applyTransactions($build, array($xaction));
|
||||
}
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($return_uri);
|
||||
}
|
||||
|
||||
switch ($command) {
|
||||
switch ($action) {
|
||||
case HarbormasterBuildCommand::COMMAND_RESTART:
|
||||
if ($issuable) {
|
||||
$title = pht('Really restart all builds?');
|
||||
|
|
|
@ -2,19 +2,13 @@
|
|||
|
||||
final class HarbormasterBuildableListController extends HarbormasterController {
|
||||
|
||||
private $queryKey;
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->queryKey = idx($data, 'queryKey');
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$controller = id(new PhabricatorApplicationSearchController())
|
||||
->setQueryKey($this->queryKey)
|
||||
->setQueryKey($request->getURIData('queryKey'))
|
||||
->setSearchEngine(new HarbormasterBuildableSearchEngine())
|
||||
->setNavigation($this->buildSideNavView());
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
|
|||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getviewer();
|
||||
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
$plan = id(new HarbormasterBuildPlanQuery())
|
||||
|
|
|
@ -4,11 +4,11 @@ final class HarbormasterStepEditController extends HarbormasterController {
|
|||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
$this->requireApplicationCapability(
|
||||
HarbormasterManagePlansCapability::CAPABILITY);
|
||||
|
||||
$id = $request->getURIData('id');
|
||||
if ($id) {
|
||||
$step = id(new HarbormasterBuildStepQuery())
|
||||
->setViewer($viewer)
|
||||
|
|
|
@ -67,11 +67,6 @@ final class HarbormasterBuildStepCoreCustomField
|
|||
$object->setDetail($key, $value);
|
||||
}
|
||||
|
||||
public function applyApplicationTransactionExternalEffects(
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
return;
|
||||
}
|
||||
|
||||
public function getBuildTargetFieldValue() {
|
||||
return $this->getProxy()->getFieldValue();
|
||||
}
|
||||
|
|
|
@ -28,9 +28,13 @@ final class HarbormasterBuildStepPHIDType extends PhabricatorPHIDType {
|
|||
foreach ($handles as $phid => $handle) {
|
||||
$build_step = $objects[$phid];
|
||||
|
||||
$id = $build_step->getID();
|
||||
$name = $build_step->getName();
|
||||
|
||||
$handle->setName($name);
|
||||
$handle
|
||||
->setName($name)
|
||||
->setFullName(pht('Build Step %d: %s', $id, $name))
|
||||
->setURI("/harbormaster/step/{$id}/edit/");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,9 +41,14 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation
|
|||
$working_copy_type = id(new DrydockWorkingCopyBlueprintImplementation())
|
||||
->getType();
|
||||
|
||||
$allowed_phids = $build_target->getFieldValue('repositoryPHIDs');
|
||||
$authorizing_phid = $build_target->getBuildStep()->getPHID();
|
||||
|
||||
$lease = DrydockLease::initializeNewLease()
|
||||
->setResourceType($working_copy_type)
|
||||
->setOwnerPHID($build_target->getPHID());
|
||||
->setOwnerPHID($build_target->getPHID())
|
||||
->setAuthorizingPHID($authorizing_phid)
|
||||
->setAllowedBlueprintPHIDs($allowed_phids);
|
||||
|
||||
$map = $this->buildRepositoryMap($build_target);
|
||||
|
||||
|
@ -104,6 +109,11 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation
|
|||
'type' => 'text',
|
||||
'required' => true,
|
||||
),
|
||||
'blueprintPHIDs' => array(
|
||||
'name' => pht('Use Blueprints'),
|
||||
'type' => 'blueprints',
|
||||
'required' => true,
|
||||
),
|
||||
'repositoryPHIDs' => array(
|
||||
'name' => pht('Also Clone'),
|
||||
'type' => 'datasource',
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
final class PhabricatorHomeQuickCreateController
|
||||
extends PhabricatorHomeController {
|
||||
|
||||
public function processRequest() {
|
||||
$viewer = $this->getRequest()->getUser();
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$items = $this->getCurrentApplication()->loadAllQuickCreateItems($viewer);
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ final class ManiphestAssignEmailCommand
|
|||
array $argv) {
|
||||
$xactions = array();
|
||||
|
||||
$assign_phid = null;
|
||||
|
||||
$assign_to = head($argv);
|
||||
if ($assign_to) {
|
||||
|
|
|
@ -18,7 +18,7 @@ final class PhabricatorMetaMTAMailgunReceiveController
|
|||
return phutil_hashes_are_identical($sig, $hash);
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
|
||||
// No CSRF for Mailgun.
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
|
@ -28,7 +28,6 @@ final class PhabricatorMetaMTAMailgunReceiveController
|
|||
pht('Mail signature is not valid. Check your Mailgun API key.'));
|
||||
}
|
||||
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
|
||||
$raw_headers = $request->getStr('headers');
|
||||
|
|
|
@ -7,12 +7,10 @@ final class PhabricatorMetaMTASendGridReceiveController
|
|||
return false;
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
|
||||
// No CSRF for SendGrid.
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
|
||||
$raw_headers = $request->getStr('headers');
|
||||
|
|
|
@ -265,6 +265,7 @@ final class MultimeterControl extends Phobject {
|
|||
'init' => true,
|
||||
'diff' => true,
|
||||
'cat' => true,
|
||||
'files' => true,
|
||||
),
|
||||
'svnadmin' => array(
|
||||
'create' => true,
|
||||
|
|
|
@ -249,6 +249,7 @@ final class PassphraseCredentialEditController extends PassphraseController {
|
|||
id(new AphrontFormPolicyControl())
|
||||
->setName('viewPolicy')
|
||||
->setPolicyObject($credential)
|
||||
->setSpacePHID($v_space)
|
||||
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
|
||||
->setPolicies($policies))
|
||||
->appendControl(
|
||||
|
|
|
@ -201,11 +201,7 @@ final class PassphraseCredentialViewController extends PassphraseController {
|
|||
pht('Description'),
|
||||
PHUIPropertyListView::ICON_SUMMARY);
|
||||
$properties->addTextContent(
|
||||
PhabricatorMarkupEngine::renderOneObject(
|
||||
id(new PhabricatorMarkupOneOff())
|
||||
->setContent($description),
|
||||
'default',
|
||||
$viewer));
|
||||
new PHUIRemarkupView($viewer, $description));
|
||||
}
|
||||
|
||||
return $properties;
|
||||
|
|
|
@ -178,4 +178,40 @@ final class PhabricatorObjectQuery
|
|||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Select invalid or restricted PHIDs from a list.
|
||||
*
|
||||
* PHIDs are invalid if their objects do not exist or can not be seen by the
|
||||
* viewer. This method is generally used to validate that PHIDs affected by
|
||||
* a transaction are valid.
|
||||
*
|
||||
* @param PhabricatorUser Viewer.
|
||||
* @param list<phid> List of ostensibly valid PHIDs.
|
||||
* @return list<phid> List of invalid or restricted PHIDs.
|
||||
*/
|
||||
public static function loadInvalidPHIDsForViewer(
|
||||
PhabricatorUser $viewer,
|
||||
array $phids) {
|
||||
|
||||
if (!$phids) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$objects = id(new PhabricatorObjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($phids)
|
||||
->execute();
|
||||
$objects = mpull($objects, null, 'getPHID');
|
||||
|
||||
$invalid = array();
|
||||
foreach ($phids as $phid) {
|
||||
if (empty($objects[$phid])) {
|
||||
$invalid[] = $phid;
|
||||
}
|
||||
}
|
||||
|
||||
return $invalid;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ final class PonderAnswerSaveController extends PonderController {
|
|||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
}
|
||||
|
||||
$answer = PonderAnswer::initializeNewAnswer($viewer);
|
||||
$answer = PonderAnswer::initializeNewAnswer($viewer, $question);
|
||||
|
||||
// Question Editor
|
||||
|
||||
|
|
|
@ -26,15 +26,18 @@ final class PonderAnswer extends PonderDAO
|
|||
|
||||
private $userVotes = array();
|
||||
|
||||
public static function initializeNewAnswer(PhabricatorUser $actor) {
|
||||
public static function initializeNewAnswer(
|
||||
PhabricatorUser $actor,
|
||||
PonderQuestion $question) {
|
||||
$app = id(new PhabricatorApplicationQuery())
|
||||
->setViewer($actor)
|
||||
->withClasses(array('PhabricatorPonderApplication'))
|
||||
->executeOne();
|
||||
|
||||
return id(new PonderAnswer())
|
||||
->setQuestionID(0)
|
||||
->setQuestionID($question->getID())
|
||||
->setContent('')
|
||||
->attachQuestion($question)
|
||||
->setAuthorPHID($actor->getPHID())
|
||||
->setVoteCount(0)
|
||||
->setStatus(PonderAnswerStatus::ANSWER_STATUS_VISIBLE);
|
||||
|
|
|
@ -112,7 +112,7 @@ final class ProjectQueryConduitAPIMethod extends ProjectConduitAPIMethod {
|
|||
$slug_map = array();
|
||||
if ($slugs) {
|
||||
foreach ($slugs as $slug) {
|
||||
$normal = rtrim(PhabricatorSlug::normalize($slug), '/');
|
||||
$normal = PhabricatorSlug::normalizeProjectSlug($slug);
|
||||
foreach ($projects as $project) {
|
||||
if (in_array($normal, $project['slugs'])) {
|
||||
$slug_map[$slug] = $project['phid'];
|
||||
|
|
|
@ -26,10 +26,17 @@ final class PhabricatorProjectViewController
|
|||
}
|
||||
$project = $query->executeOne();
|
||||
if (!$project) {
|
||||
|
||||
// If this request corresponds to a project but just doesn't have the
|
||||
// slug quite right, redirect to the proper URI.
|
||||
$uri = $this->getNormalizedURI($slug);
|
||||
if ($uri !== null) {
|
||||
return id(new AphrontRedirectResponse())->setURI($uri);
|
||||
}
|
||||
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
|
||||
$columns = id(new PhabricatorProjectColumnQuery())
|
||||
->setViewer($viewer)
|
||||
->withProjectPHIDs(array($project->getPHID()))
|
||||
|
@ -53,4 +60,31 @@ final class PhabricatorProjectViewController
|
|||
return $this->delegateToController($controller_object);
|
||||
}
|
||||
|
||||
private function getNormalizedURI($slug) {
|
||||
if (!strlen($slug)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$normal = PhabricatorSlug::normalizeProjectSlug($slug);
|
||||
if ($normal === $slug) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
// Do execute() instead of executeOne() here so we canonicalize before
|
||||
// raising a policy exception. This is a little more polished than letting
|
||||
// the user hit the error on any variant of the slug.
|
||||
|
||||
$projects = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withSlugs(array($normal))
|
||||
->execute();
|
||||
if (!$projects) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return "/tag/{$normal}/";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -81,9 +81,9 @@ final class PhabricatorProjectTransactionEditor
|
|||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorProjectTransaction::TYPE_NAME:
|
||||
$object->setName($xaction->getNewValue());
|
||||
// TODO - this is really "setPrimarySlug"
|
||||
$object->setPhrictionSlug($xaction->getNewValue());
|
||||
$name = $xaction->getNewValue();
|
||||
$object->setName($name);
|
||||
$object->setPrimarySlug(PhabricatorSlug::normalizeProjectSlug($name));
|
||||
return;
|
||||
case PhabricatorProjectTransaction::TYPE_SLUGS:
|
||||
return;
|
||||
|
@ -265,9 +265,8 @@ final class PhabricatorProjectTransactionEditor
|
|||
$errors[] = $error;
|
||||
}
|
||||
|
||||
$slug_builder = clone $object;
|
||||
$slug_builder->setPhrictionSlug($name);
|
||||
$slug = $slug_builder->getPrimarySlug();
|
||||
$slug = PhabricatorSlug::normalizeProjectSlug($name);
|
||||
|
||||
$slug_used_already = id(new PhabricatorProjectSlug())
|
||||
->loadOneWhere('slug = %s', $slug);
|
||||
if ($slug_used_already &&
|
||||
|
@ -498,9 +497,7 @@ final class PhabricatorProjectTransactionEditor
|
|||
PhabricatorLiskDAO $object,
|
||||
$name) {
|
||||
|
||||
$object = (clone $object);
|
||||
$object->setPhrictionSlug($name);
|
||||
$slug = $object->getPrimarySlug();
|
||||
$slug = PhabricatorSlug::normalizeProjectSlug($name);
|
||||
|
||||
$slug_object = id(new PhabricatorProjectSlug())->loadOneWhere(
|
||||
'slug = %s',
|
||||
|
|
|
@ -7,7 +7,6 @@ final class PhabricatorProjectQuery
|
|||
private $phids;
|
||||
private $memberPHIDs;
|
||||
private $slugs;
|
||||
private $phrictionSlugs;
|
||||
private $names;
|
||||
private $nameTokens;
|
||||
private $icons;
|
||||
|
@ -50,11 +49,6 @@ final class PhabricatorProjectQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withPhrictionSlugs(array $slugs) {
|
||||
$this->phrictionSlugs = $slugs;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withNames(array $names) {
|
||||
$this->names = $names;
|
||||
return $this;
|
||||
|
@ -308,13 +302,6 @@ final class PhabricatorProjectQuery
|
|||
$this->slugs);
|
||||
}
|
||||
|
||||
if ($this->phrictionSlugs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'phrictionSlug IN (%Ls)',
|
||||
$this->phrictionSlugs);
|
||||
}
|
||||
|
||||
if ($this->names !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
|
|
|
@ -189,24 +189,11 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
|||
return $this->assertAttached($this->memberPHIDs);
|
||||
}
|
||||
|
||||
public function setPhrictionSlug($slug) {
|
||||
|
||||
// NOTE: We're doing a little magic here and stripping out '/' so that
|
||||
// project pages always appear at top level under projects/ even if the
|
||||
// display name is "Hack / Slash" or similar (it will become
|
||||
// 'hack_slash' instead of 'hack/slash').
|
||||
|
||||
$slug = str_replace('/', ' ', $slug);
|
||||
$slug = PhabricatorSlug::normalize($slug);
|
||||
$this->phrictionSlug = $slug;
|
||||
public function setPrimarySlug($slug) {
|
||||
$this->phrictionSlug = $slug.'/';
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFullPhrictionSlug() {
|
||||
$slug = $this->getPhrictionSlug();
|
||||
return 'projects/'.$slug;
|
||||
}
|
||||
|
||||
// TODO - once we sever project => phriction automagicalness,
|
||||
// migrate getPhrictionSlug to have no trailing slash and be called
|
||||
// getPrimarySlug
|
||||
|
|
|
@ -19,4 +19,22 @@ final class PhabricatorRepositoryVersion extends Phobject {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The `locate` command is deprecated as of Mercurial 3.2, to be
|
||||
* replaced with `files` command, which supports most of the same
|
||||
* arguments. This determines whether the new `files` command should
|
||||
* be used instead of the `locate` command.
|
||||
*
|
||||
* @param string $mercurial_version - The current version of mercurial
|
||||
* which can be retrieved by calling:
|
||||
* PhabricatorRepositoryVersion::getMercurialVersion()
|
||||
*
|
||||
* @return boolean True if the version of Mercurial is new enough to support
|
||||
* the `files` command, or false if otherwise.
|
||||
*/
|
||||
public static function isMercurialFilesCommandAvailable($mercurial_version) {
|
||||
$min_version_for_files = '3.2';
|
||||
return version_compare($mercurial_version, $min_version_for_files, '>=');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ final class PhabricatorRepositoryEditor
|
|||
$types[] = PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE;
|
||||
$types[] = PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES;
|
||||
$types[] = PhabricatorRepositoryTransaction::TYPE_STAGING_URI;
|
||||
$types[] = PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS;
|
||||
|
||||
$types[] = PhabricatorTransactions::TYPE_EDGE;
|
||||
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
|
||||
|
@ -107,6 +108,8 @@ final class PhabricatorRepositoryEditor
|
|||
return $object->getSymbolSources();
|
||||
case PhabricatorRepositoryTransaction::TYPE_STAGING_URI:
|
||||
return $object->getDetail('staging-uri');
|
||||
case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS:
|
||||
return $object->getDetail('automation.blueprintPHIDs', array());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,6 +146,7 @@ final class PhabricatorRepositoryEditor
|
|||
case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE:
|
||||
case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES:
|
||||
case PhabricatorRepositoryTransaction::TYPE_STAGING_URI:
|
||||
case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS:
|
||||
return $xaction->getNewValue();
|
||||
case PhabricatorRepositoryTransaction::TYPE_NOTIFY:
|
||||
case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE:
|
||||
|
@ -226,6 +230,11 @@ final class PhabricatorRepositoryEditor
|
|||
case PhabricatorRepositoryTransaction::TYPE_STAGING_URI:
|
||||
$object->setDetail('staging-uri', $xaction->getNewValue());
|
||||
return;
|
||||
case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS:
|
||||
$object->setDetail(
|
||||
'automation.blueprintPHIDs',
|
||||
$xaction->getNewValue());
|
||||
return;
|
||||
case PhabricatorRepositoryTransaction::TYPE_ENCODING:
|
||||
// Make sure the encoding is valid by converting to UTF-8. This tests
|
||||
// that the user has mbstring installed, and also that they didn't type
|
||||
|
@ -276,33 +285,17 @@ final class PhabricatorRepositoryEditor
|
|||
|
||||
$editor->save();
|
||||
break;
|
||||
case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS:
|
||||
DrydockAuthorization::applyAuthorizationChanges(
|
||||
$this->getActor(),
|
||||
$object->getPHID(),
|
||||
$xaction->getOldValue(),
|
||||
$xaction->getNewValue());
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function mergeTransactions(
|
||||
PhabricatorApplicationTransaction $u,
|
||||
PhabricatorApplicationTransaction $v) {
|
||||
|
||||
$type = $u->getTransactionType();
|
||||
switch ($type) {}
|
||||
|
||||
return parent::mergeTransactions($u, $v);
|
||||
}
|
||||
|
||||
protected function transactionHasEffect(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
$old = $xaction->getOldValue();
|
||||
$new = $xaction->getNewValue();
|
||||
|
||||
$type = $xaction->getTransactionType();
|
||||
switch ($type) {}
|
||||
|
||||
return parent::transactionHasEffect($object, $xaction);
|
||||
}
|
||||
|
||||
protected function requireCapabilities(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
@ -338,6 +331,7 @@ final class PhabricatorRepositoryEditor
|
|||
case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES:
|
||||
case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE:
|
||||
case PhabricatorRepositoryTransaction::TYPE_STAGING_URI:
|
||||
case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS:
|
||||
PhabricatorPolicyFilter::requireCapability(
|
||||
$this->requireActor(),
|
||||
$object,
|
||||
|
@ -431,6 +425,29 @@ final class PhabricatorRepositoryEditor
|
|||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS:
|
||||
foreach ($xactions as $xaction) {
|
||||
$old = nonempty($xaction->getOldValue(), array());
|
||||
$new = nonempty($xaction->getNewValue(), array());
|
||||
|
||||
$add = array_diff($new, $old);
|
||||
|
||||
$invalid = PhabricatorObjectQuery::loadInvalidPHIDsForViewer(
|
||||
$this->getActor(),
|
||||
$add);
|
||||
if ($invalid) {
|
||||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Invalid'),
|
||||
pht(
|
||||
'Some of the selected automation blueprints are invalid '.
|
||||
'or restricted: %s.',
|
||||
implode(', ', $invalid)),
|
||||
$xaction);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $errors;
|
||||
|
|
|
@ -1799,7 +1799,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
}
|
||||
|
||||
|
||||
/* -( Staging )-------------------------------------------------------------*/
|
||||
/* -( Staging )------------------------------------------------------------ */
|
||||
|
||||
|
||||
public function supportsStaging() {
|
||||
|
@ -1815,6 +1815,33 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
}
|
||||
|
||||
|
||||
/* -( Automation )--------------------------------------------------------- */
|
||||
|
||||
|
||||
public function supportsAutomation() {
|
||||
return $this->isGit();
|
||||
}
|
||||
|
||||
public function canPerformAutomation() {
|
||||
if (!$this->supportsAutomation()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->getAutomationBlueprintPHIDs()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getAutomationBlueprintPHIDs() {
|
||||
if (!$this->supportsAutomation()) {
|
||||
return array();
|
||||
}
|
||||
return $this->getDetail('automation.blueprintPHIDs', array());
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
||||
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ final class PhabricatorRepositoryTransaction
|
|||
const TYPE_SYMBOLS_SOURCES = 'repo:symbol-source';
|
||||
const TYPE_SYMBOLS_LANGUAGE = 'repo:symbol-language';
|
||||
const TYPE_STAGING_URI = 'repo:staging-uri';
|
||||
const TYPE_AUTOMATION_BLUEPRINTS = 'repo:automation-blueprints';
|
||||
|
||||
// TODO: Clean up these legacy transaction types.
|
||||
const TYPE_SSH_LOGIN = 'repo:ssh-login';
|
||||
|
@ -65,6 +66,7 @@ final class PhabricatorRepositoryTransaction
|
|||
}
|
||||
break;
|
||||
case self::TYPE_SYMBOLS_SOURCES:
|
||||
case self::TYPE_AUTOMATION_BLUEPRINTS:
|
||||
if ($old) {
|
||||
$phids = array_merge($phids, $old);
|
||||
}
|
||||
|
@ -436,6 +438,34 @@ final class PhabricatorRepositoryTransaction
|
|||
$old,
|
||||
$new);
|
||||
}
|
||||
|
||||
case self::TYPE_AUTOMATION_BLUEPRINTS:
|
||||
$add = array_diff($new, $old);
|
||||
$rem = array_diff($old, $new);
|
||||
|
||||
if ($add && $rem) {
|
||||
return pht(
|
||||
'%s changed %s automation blueprint(s), '.
|
||||
'added %s: %s; removed %s: %s.',
|
||||
$this->renderHandleLink($author_phid),
|
||||
new PhutilNumber(count($add) + count($rem)),
|
||||
new PhutilNumber(count($add)),
|
||||
$this->renderHandleList($add),
|
||||
new PhutilNumber(count($rem)),
|
||||
$this->renderHandleList($rem));
|
||||
} else if ($add) {
|
||||
return pht(
|
||||
'%s added %s automation blueprint(s): %s.',
|
||||
$this->renderHandleLink($author_phid),
|
||||
new PhutilNumber(count($add)),
|
||||
$this->renderHandleList($add));
|
||||
} else {
|
||||
return pht(
|
||||
'%s removed %s automation blueprint(s): %s.',
|
||||
$this->renderHandleLink($author_phid),
|
||||
new PhutilNumber(count($rem)),
|
||||
$this->renderHandleList($rem));
|
||||
}
|
||||
}
|
||||
|
||||
return parent::getTitle();
|
||||
|
|
|
@ -58,3 +58,12 @@ a corresponding resource by either finding a suitable unused resource or
|
|||
creating a new resource. When work completes, the resource is returned to the
|
||||
resource pool or destroyed.
|
||||
|
||||
Next Steps
|
||||
==========
|
||||
|
||||
Continue by:
|
||||
|
||||
- understanding Drydock security concerns with
|
||||
@{article:Drydock User Guide: Security}; or
|
||||
- allowing Phabricator to write to repositories with
|
||||
@{article:Drydock User Guide: Repository Automation}.
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
@title Drydock User Guide: Repository Automation
|
||||
@group userguide
|
||||
|
||||
Configuring repository automation so Phabricator can push commits.
|
||||
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
IMPORTANT: This feature is very new and most of the capabilities described
|
||||
in this document are not yet available. This feature as a whole is a prototype.
|
||||
|
||||
By configuring Drydock and Diffusion appropriately, you can enable **Repository
|
||||
Automation** for a repository. Once automation is set up, Phabricator will be
|
||||
able to make changes to the repository.
|
||||
|
||||
|
||||
Security
|
||||
========
|
||||
|
||||
Configuring repository automation amounts to telling Phabricator where it
|
||||
should perform working copy operations (like merges, cherry-picks and pushes)
|
||||
when doing writes.
|
||||
|
||||
Depending on how stringent you are about change control, you may want to
|
||||
make sure these processes are isolated and can not be tampered with. If you
|
||||
run tests and automation on the same hardware, tests may be able to interfere
|
||||
with automation. You can read more about this in
|
||||
@{article:Drydock User Guide: Security}.
|
||||
|
||||
|
||||
Next Steps
|
||||
==========
|
||||
|
||||
Continue by:
|
||||
|
||||
- understanding Drydock security concerns with
|
||||
@{article:Drydock User Guide: Security}; or
|
||||
- returning to the @{article:Drydock User Guide}.
|
209
src/docs/user/userguide/drydock_security.diviner
Normal file
209
src/docs/user/userguide/drydock_security.diviner
Normal file
|
@ -0,0 +1,209 @@
|
|||
@title Drydock User Guide: Security
|
||||
@group userguide
|
||||
|
||||
Understanding security concerns in Drydock.
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
Different applications use Drydock for different things, and some of the things
|
||||
they do with Drydock require different levels of trust and access. It is
|
||||
important to configure Drydock properly so that less trusted code can't do
|
||||
anything you aren't comfortable with.
|
||||
|
||||
For example, running unit tests on Drydock normally involves running relatively
|
||||
untrusted code (it often has a single author and has not yet been reviewed)
|
||||
that needs very few capabilities (generally, it only needs to be able to report
|
||||
results back to Phabricator). In contrast, automating merge requests on Drydock
|
||||
involves running trusted code that needs more access (it must be able to write
|
||||
to repositories).
|
||||
|
||||
Drydock allows resources to be shared and reused, so it's possible to configure
|
||||
Drydock in a way that gives untrusted code a lot of access by accident.
|
||||
|
||||
One way Drydock makes allocations faster is by sharing, reusing, and recycling
|
||||
resources. When an application asks Drydock for a working copy, it will try to
|
||||
satisfy the request by cleaning up a suitable existing working copy if it can,
|
||||
instead of building a new one. This is faster, but it means that tasks have
|
||||
some ability to interact or interfere with each other.
|
||||
|
||||
Similarly, Drydock may allocate multiple leases on the same host at the same
|
||||
time, running as the same user. This is generally simpler to configure and less
|
||||
wasteful than fully isolating leases, but means that they can interact.
|
||||
|
||||
Depending on your organization, environment and use cases, you might not want
|
||||
this, and it may be important that different use cases are unable to interfere
|
||||
with each other. For example, you might want to prevent unit tests from writing
|
||||
to repositories.
|
||||
|
||||
**Drydock does not guarantee that resources are isolated by default**. When
|
||||
resources are more isolated, they are usually also harder to configure and
|
||||
slower to allocate. Because most installs will want to find a balance between
|
||||
isolation and complexity/performance, Drydock does not make assumptions about
|
||||
either isolation or performance having absolute priority.
|
||||
|
||||
You'll usually want to isolate things just enough that nothing bad can happen.
|
||||
Fortunately, this is straightforward. This document describes how to make sure
|
||||
you have enough isolation so that nothing you're uncomfortable with can occur.
|
||||
|
||||
|
||||
Choosing an Isolation Policy
|
||||
============================
|
||||
|
||||
This section provides some reasonable examples of ways you might approach
|
||||
configuring Drydock.
|
||||
|
||||
| Isolation | Suitable For | Description
|
||||
|-----------|-----|-------
|
||||
| Zero | Development | Everything on one host.
|
||||
| Low | Small Installs | Use a dedicated Drydock host.
|
||||
| High | Most Installs | **Recommended**. Use low-trust and high-trust pools.
|
||||
| Custom | Special Requirements | Use multiple pools.
|
||||
| Absolute | Special Requirements | Completely isolate all resources.
|
||||
|
||||
**Zero Isolation**: Run Drydock operations on the same host that Phabricator
|
||||
runs on. This is only suitable for developing or testing Phabricator. Any
|
||||
Drydock operation can potentially compromise Phabricator. It is intentionally
|
||||
difficult to configure Drydock to operate in this mode. Running Drydock
|
||||
operations on the Phabricator host is strongly discouraged.
|
||||
|
||||
**Low Isolation**: Designate a separate Drydock host and run Drydock
|
||||
operations on it. This is suitable for small installs and provides a reasonable
|
||||
level of isolation. However, it will allow unit tests (which often run
|
||||
lower-trust code) to interfere with repository automation operations.
|
||||
|
||||
**High Isolation**: Designate two Drydock host pools and run low-trust
|
||||
operations (like builds) on one pool and high-trust operations (like repository
|
||||
automation) on a separate pool. This provides a good balance between isolation
|
||||
and performance, although tests can still potentially interfere with the
|
||||
execution of unrelated tests.
|
||||
|
||||
**Custom Isolation**: You can continue adding pools to refine the resource
|
||||
isolation model. For example, you may have higher-trust and lower-trust
|
||||
repositories or do builds on a mid-trust tier which runs only reviewed code.
|
||||
|
||||
**Absolute Isolation**: Configure blueprints to completely initialize and
|
||||
destroy hosts or containers on every request, and limit all resources to one
|
||||
simultaneous lease. This will completely isolate every operation, but come at
|
||||
a high performance and complexity cost.
|
||||
|
||||
NOTE: It is not currently possible to configure Drydock in an absolute
|
||||
isolation mode.
|
||||
|
||||
It is usually reasonable to choose one of these approaches as a starting point
|
||||
and then adjust it to fit your requirements. You can also evolve your use of
|
||||
Drydock over time as your needs change.
|
||||
|
||||
|
||||
Threat Scenarios
|
||||
================
|
||||
|
||||
This section will help you understand the threats to a Drydock environment.
|
||||
Not all threats will be concerning to all installs, and you can choose an
|
||||
approach which defuses only the threats you care about.
|
||||
|
||||
Attackers have three primary targets:
|
||||
|
||||
- capturing hosts;
|
||||
- compromising Phabricator; and
|
||||
- compromising the integrity of other Drydock processes.
|
||||
|
||||
**Attacks against hosts** are the least sophisticated. In this scenario, an
|
||||
attacker wants to run a program like a Bitcoin miner or botnet client on
|
||||
hardware that they aren't paying for or which can't be traced to them. They
|
||||
write a "unit test" or which launches this software, then send a revision
|
||||
containing this "unit test" for review. If Phabricator is configured to
|
||||
automatically run tests on new revisions, it may execute automatically and give
|
||||
the attacker access to computing resources they did not previously control and
|
||||
which can not easily be traced back to them.
|
||||
|
||||
This is usually only a meaningful threat for open source installs, because
|
||||
there is a high probability of eventual detection and the value of these
|
||||
resources is small, so employees will generally not have an incentive to
|
||||
attempt this sort of attack. The easiest way to prevent this attack is to
|
||||
prevent untrusted, anonymous contributors from running tests. For example,
|
||||
create a "Trusted Contributors" project and only run tests if a revision author
|
||||
is a member of the project.
|
||||
|
||||
**Attacks against Phabricator** are more sophisticated. In this scenario, an
|
||||
attacker tries to compromise Phabricator itself (for example, to make themselves
|
||||
an administrator or gain access to an administrator account).
|
||||
|
||||
This is made possible if Drydock is running on the same host as Phabricator or
|
||||
runs on a privileged subnet with access to resources like Phabricator database
|
||||
hosts. Most installs should be concerned about this attack.
|
||||
|
||||
The best way to defuse this attack is to run Drydock processes on a separate
|
||||
host which is not on a privileged subnet. For example, use a
|
||||
`build.mycompany.com` host or pool for Drydock processes, separate from your
|
||||
`phabricator.mycompany.com` host or pool.
|
||||
|
||||
Even if the host is not privileged, many Drydock processes have some level of
|
||||
privilege (enabling them to clone repositories, or report test results back to
|
||||
Phabricator). Be aware that tests can hijack credentials they are run with,
|
||||
and potentialy hijack credentials given to other processes on the same hosts.
|
||||
You should use credentials with a minimum set of privileges and assume all
|
||||
processes on a host have the highest level of access that any process on the
|
||||
host has.
|
||||
|
||||
**Attacks against Drydock** are the most sophisticated. In this scenario, an
|
||||
attacker uses one Drydock process to compromise a different process: for
|
||||
example, a unit test which tampers with a merge or injects code into a build.
|
||||
This might allow an attacker to make changes to a repository or binary without
|
||||
going through review or triggering other rules which would normally detect the
|
||||
change.
|
||||
|
||||
These attackers could also make failing tests appear to pass, or break tests or
|
||||
builds, but these attacks are generally less interesting than tampering with
|
||||
a repository or binary.
|
||||
|
||||
This is a complex attack which you may not have to worry about unless you have
|
||||
a high degree of process and control in your change pipeline. If users can push
|
||||
changes directly to repositories, this often represents a faster and easier way
|
||||
to achieve the same tampering.
|
||||
|
||||
The best way to defuse this attack is to prevent high-trust (repository
|
||||
automation) processes from running on the same hosts as low-trust (unit test)
|
||||
processes. For example, use an `automation.mycompany.com` host or pool for
|
||||
repository automation, and a `build.mycompany.com` host or pool for tests.
|
||||
|
||||
|
||||
Applying an Isolation Policy
|
||||
============================
|
||||
|
||||
Designing a security and isolation policy for Drydock can take some thought,
|
||||
but applying it is straightforward. Applications which want to use Drydock must
|
||||
explicitly list which blueprints they are allowed to use, and they must be
|
||||
approved to use them in Drydock. By default, nothing can do anything, which is
|
||||
very safe and secure.
|
||||
|
||||
To get builds or automation running on a host, specify the host blueprint as a
|
||||
usable blueprint in the build step or repository configuration. This creates a
|
||||
new authorization request in Drydock which must be approved before things can
|
||||
move forward.
|
||||
|
||||
Until the authorization is approved, the process can not use the blueprint to
|
||||
create any resources, nor can it use resources previously created by the
|
||||
blueprint.
|
||||
|
||||
You can review and approve requests from the blueprint detail view in Drydock:
|
||||
find the request and click {nav Approve Authorization}. You can also revoke
|
||||
approval at any time from this screen which will prevent the object from
|
||||
continuing to use the blueprint (but note that this does not release any
|
||||
existing leases).
|
||||
|
||||
Once the authorization request is approved, the build or automation process
|
||||
should be able to run if everything else is configured properly.
|
||||
|
||||
Note that authorizations are transitive: if a build step is authorized to use
|
||||
blueprint A, and blueprint A is authorized to use blueprint B, the build step
|
||||
may indirectly operate on resources created by blueprint B. This should
|
||||
normally be consistent with expectations.
|
||||
|
||||
|
||||
Next Steps
|
||||
==========
|
||||
|
||||
Continue by:
|
||||
|
||||
- returning to the @{article:Drydock User Guide}.
|
|
@ -540,9 +540,7 @@ abstract class PhabricatorCustomField extends Phobject {
|
|||
* @task storage
|
||||
*/
|
||||
public function newStorageObject() {
|
||||
if ($this->proxy) {
|
||||
return $this->proxy->newStorageObject();
|
||||
}
|
||||
// NOTE: This intentionally isn't proxied, to avoid call cycles.
|
||||
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue