diff --git a/resources/sql/autopatches/20151010.drydock.auth.2.sql b/resources/sql/autopatches/20151010.drydock.auth.2.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20151010.drydock.auth.2.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_drydock.drydock_lease + ADD authorizingPHID VARBINARY(64) NOT NULL; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -847,6 +847,8 @@ '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', @@ -4611,6 +4613,8 @@ 'DrydockLeaseDestroyedLogType' => 'DrydockLogType', 'DrydockLeaseListController' => 'DrydockLeaseController', 'DrydockLeaseListView' => 'AphrontView', + 'DrydockLeaseNoAuthorizationsLogType' => 'DrydockLogType', + 'DrydockLeaseNoBlueprintsLogType' => 'DrydockLogType', 'DrydockLeasePHIDType' => 'PhabricatorPHIDType', 'DrydockLeaseQuery' => 'DrydockQuery', 'DrydockLeaseQueuedLogType' => 'DrydockLogType', diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php --- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php @@ -292,7 +292,8 @@ } protected function newLease(DrydockBlueprint $blueprint) { - return DrydockLease::initializeNewLease(); + return DrydockLease::initializeNewLease() + ->setAuthorizingPHID($blueprint->getPHID()); } protected function requireActiveLease(DrydockLease $lease) { diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php --- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php @@ -118,10 +118,13 @@ $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()) diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php --- a/src/applications/drydock/controller/DrydockLeaseViewController.php +++ b/src/applications/drydock/controller/DrydockLeaseViewController.php @@ -116,6 +116,14 @@ } $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); diff --git a/src/applications/drydock/logtype/DrydockLeaseNoAuthorizationsLogType.php b/src/applications/drydock/logtype/DrydockLeaseNoAuthorizationsLogType.php new file mode 100644 --- /dev/null +++ b/src/applications/drydock/logtype/DrydockLeaseNoAuthorizationsLogType.php @@ -0,0 +1,26 @@ +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()); + } + +} diff --git a/src/applications/drydock/logtype/DrydockLeaseNoBlueprintsLogType.php b/src/applications/drydock/logtype/DrydockLeaseNoBlueprintsLogType.php new file mode 100644 --- /dev/null +++ b/src/applications/drydock/logtype/DrydockLeaseNoBlueprintsLogType.php @@ -0,0 +1,19 @@ +getViewer(); $resource_type = $args->getArg('type'); if (!$resource_type) { @@ -59,6 +59,23 @@ $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); } diff --git a/src/applications/drydock/phid/DrydockBlueprintPHIDType.php b/src/applications/drydock/phid/DrydockBlueprintPHIDType.php --- a/src/applications/drydock/phid/DrydockBlueprintPHIDType.php +++ b/src/applications/drydock/phid/DrydockBlueprintPHIDType.php @@ -8,6 +8,14 @@ return pht('Blueprint'); } + public function getPHIDTypeApplicationClass() { + return 'PhabricatorDrydockApplication'; + } + + public function getTypeIcon() { + return 'fa-map-o'; + } + public function newObject() { return new DrydockBlueprint(); } diff --git a/src/applications/drydock/phid/DrydockLeasePHIDType.php b/src/applications/drydock/phid/DrydockLeasePHIDType.php --- a/src/applications/drydock/phid/DrydockLeasePHIDType.php +++ b/src/applications/drydock/phid/DrydockLeasePHIDType.php @@ -8,6 +8,14 @@ return pht('Drydock Lease'); } + public function getPHIDTypeApplicationClass() { + return 'PhabricatorDrydockApplication'; + } + + public function getTypeIcon() { + return 'fa-link'; + } + public function newObject() { return new DrydockLease(); } diff --git a/src/applications/drydock/phid/DrydockResourcePHIDType.php b/src/applications/drydock/phid/DrydockResourcePHIDType.php --- a/src/applications/drydock/phid/DrydockResourcePHIDType.php +++ b/src/applications/drydock/phid/DrydockResourcePHIDType.php @@ -8,6 +8,14 @@ return pht('Drydock Resource'); } + public function getPHIDTypeApplicationClass() { + return 'PhabricatorDrydockApplication'; + } + + public function getTypeIcon() { + return 'fa-map'; + } + public function newObject() { return new DrydockResource(); } diff --git a/src/applications/drydock/query/DrydockBlueprintQuery.php b/src/applications/drydock/query/DrydockBlueprintQuery.php --- a/src/applications/drydock/query/DrydockBlueprintQuery.php +++ b/src/applications/drydock/query/DrydockBlueprintQuery.php @@ -7,6 +7,7 @@ private $blueprintClasses; private $datasourceQuery; private $disabled; + private $authorizedPHIDs; public function withIDs(array $ids) { $this->ids = $ids; @@ -33,10 +34,19 @@ 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 @@ 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; + } + } diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -7,6 +7,7 @@ protected $resourceType; protected $until; protected $ownerPHID; + protected $authorizingPHID; protected $attributes = array(); protected $status = DrydockLeaseStatus::STATUS_PENDING; @@ -141,6 +142,25 @@ 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 @@ 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; diff --git a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php --- a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php @@ -300,11 +300,46 @@ 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) { diff --git a/src/applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php b/src/applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php --- a/src/applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php +++ b/src/applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php @@ -67,11 +67,6 @@ $object->setDetail($key, $value); } - public function applyApplicationTransactionExternalEffects( - PhabricatorApplicationTransaction $xaction) { - return; - } - public function getBuildTargetFieldValue() { return $this->getProxy()->getFieldValue(); } diff --git a/src/applications/harbormaster/phid/HarbormasterBuildStepPHIDType.php b/src/applications/harbormaster/phid/HarbormasterBuildStepPHIDType.php --- a/src/applications/harbormaster/phid/HarbormasterBuildStepPHIDType.php +++ b/src/applications/harbormaster/phid/HarbormasterBuildStepPHIDType.php @@ -28,9 +28,13 @@ 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/"); } } diff --git a/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php --- a/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php @@ -41,9 +41,14 @@ $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 @@ 'type' => 'text', 'required' => true, ), + 'blueprintPHIDs' => array( + 'name' => pht('Use Blueprints'), + 'type' => 'blueprints', + 'required' => true, + ), 'repositoryPHIDs' => array( 'name' => pht('Also Clone'), 'type' => 'datasource',