@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Use Drydock authorizations when acquiring leases

Summary:
Ref T9519. When acquiring leases on resources:

- Only consider resources created by authorized blueprints.
- Only consider authorized blueprints when creating new resources.
- Fail with a tailored error if no blueprints are allowed.
- Fail with a tailored error if missing authorizations are causing acquisition failure.

One somewhat-substantial issue with this is that it's pretty hard to figure out from the Harbormaster side. Specifically, the Build step UI does not show field value anywhere, so the presence of unapproved blueprints is not communicated. This is much more clear in Drydock. I'll plan to address this in future changes to Harbormaster, since there are other related/similar issues anyway.

Test Plan: {F872527}

Reviewers: hach-que, chad

Reviewed By: chad

Maniphest Tasks: T9519

Differential Revision: https://secure.phabricator.com/D14254

+232 -18
+2
resources/sql/autopatches/20151010.drydock.auth.2.sql
··· 1 + ALTER TABLE {$NAMESPACE}_drydock.drydock_lease 2 + ADD authorizingPHID VARBINARY(64) NOT NULL;
+4
src/__phutil_library_map__.php
··· 847 847 'DrydockLeaseDestroyedLogType' => 'applications/drydock/logtype/DrydockLeaseDestroyedLogType.php', 848 848 'DrydockLeaseListController' => 'applications/drydock/controller/DrydockLeaseListController.php', 849 849 'DrydockLeaseListView' => 'applications/drydock/view/DrydockLeaseListView.php', 850 + 'DrydockLeaseNoAuthorizationsLogType' => 'applications/drydock/logtype/DrydockLeaseNoAuthorizationsLogType.php', 851 + 'DrydockLeaseNoBlueprintsLogType' => 'applications/drydock/logtype/DrydockLeaseNoBlueprintsLogType.php', 850 852 'DrydockLeasePHIDType' => 'applications/drydock/phid/DrydockLeasePHIDType.php', 851 853 'DrydockLeaseQuery' => 'applications/drydock/query/DrydockLeaseQuery.php', 852 854 'DrydockLeaseQueuedLogType' => 'applications/drydock/logtype/DrydockLeaseQueuedLogType.php', ··· 4611 4613 'DrydockLeaseDestroyedLogType' => 'DrydockLogType', 4612 4614 'DrydockLeaseListController' => 'DrydockLeaseController', 4613 4615 'DrydockLeaseListView' => 'AphrontView', 4616 + 'DrydockLeaseNoAuthorizationsLogType' => 'DrydockLogType', 4617 + 'DrydockLeaseNoBlueprintsLogType' => 'DrydockLogType', 4614 4618 'DrydockLeasePHIDType' => 'PhabricatorPHIDType', 4615 4619 'DrydockLeaseQuery' => 'DrydockQuery', 4616 4620 'DrydockLeaseQueuedLogType' => 'DrydockLogType',
+2 -1
src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
··· 292 292 } 293 293 294 294 protected function newLease(DrydockBlueprint $blueprint) { 295 - return DrydockLease::initializeNewLease(); 295 + return DrydockLease::initializeNewLease() 296 + ->setAuthorizingPHID($blueprint->getPHID()); 296 297 } 297 298 298 299 protected function requireActiveLease(DrydockLease $lease) {
+4 -1
src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
··· 118 118 119 119 $resource_phid = $resource->getPHID(); 120 120 121 + $blueprint_phids = $blueprint->getFieldValue('blueprintPHIDs'); 122 + 121 123 $host_lease = $this->newLease($blueprint) 122 124 ->setResourceType('host') 123 125 ->setOwnerPHID($resource_phid) 124 - ->setAttribute('workingcopy.resourcePHID', $resource_phid); 126 + ->setAttribute('workingcopy.resourcePHID', $resource_phid) 127 + ->setAllowedBlueprintPHIDs($blueprint_phids); 125 128 126 129 $resource 127 130 ->setAttribute('host.leasePHID', $host_lease->getPHID())
+8
src/applications/drydock/controller/DrydockLeaseViewController.php
··· 116 116 } 117 117 $view->addProperty(pht('Owner'), $owner_display); 118 118 119 + $authorizing_phid = $lease->getAuthorizingPHID(); 120 + if ($authorizing_phid) { 121 + $authorizing_display = $viewer->renderHandle($authorizing_phid); 122 + } else { 123 + $authorizing_display = phutil_tag('em', array(), pht('None')); 124 + } 125 + $view->addProperty(pht('Authorized By'), $authorizing_display); 126 + 119 127 $resource_phid = $lease->getResourcePHID(); 120 128 if ($resource_phid) { 121 129 $resource_display = $viewer->renderHandle($resource_phid);
+26
src/applications/drydock/logtype/DrydockLeaseNoAuthorizationsLogType.php
··· 1 + <?php 2 + 3 + final class DrydockLeaseNoAuthorizationsLogType extends DrydockLogType { 4 + 5 + const LOGCONST = 'core.lease.no-authorizations'; 6 + 7 + public function getLogTypeName() { 8 + return pht('No Authorizations'); 9 + } 10 + 11 + public function getLogTypeIcon(array $data) { 12 + return 'fa-map-o red'; 13 + } 14 + 15 + public function renderLog(array $data) { 16 + $viewer = $this->getViewer(); 17 + $authorizing_phid = idx($data, 'authorizingPHID'); 18 + 19 + return pht( 20 + 'The object which authorized this lease (%s) is not authorized to use '. 21 + 'any of the blueprints the lease lists. Approve the authorizations '. 22 + 'before using the lease.', 23 + $viewer->renderHandle($authorizing_phid)->render()); 24 + } 25 + 26 + }
+19
src/applications/drydock/logtype/DrydockLeaseNoBlueprintsLogType.php
··· 1 + <?php 2 + 3 + final class DrydockLeaseNoBlueprintsLogType extends DrydockLogType { 4 + 5 + const LOGCONST = 'core.lease.no-blueprints'; 6 + 7 + public function getLogTypeName() { 8 + return pht('No Blueprints'); 9 + } 10 + 11 + public function getLogTypeIcon(array $data) { 12 + return 'fa-map-o red'; 13 + } 14 + 15 + public function renderLog(array $data) { 16 + return pht('This lease does not list any usable blueprints.'); 17 + } 18 + 19 + }
+18 -1
src/applications/drydock/management/DrydockManagementLeaseWorkflow.php
··· 28 28 } 29 29 30 30 public function execute(PhutilArgumentParser $args) { 31 - $console = PhutilConsole::getConsole(); 31 + $viewer = $this->getViewer(); 32 32 33 33 $resource_type = $args->getArg('type'); 34 34 if (!$resource_type) { ··· 58 58 59 59 $lease = id(new DrydockLease()) 60 60 ->setResourceType($resource_type); 61 + 62 + $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); 63 + $lease->setAuthorizingPHID($drydock_phid); 64 + 65 + // TODO: This is not hugely scalable, although this is a debugging workflow 66 + // so maybe it's fine. Do we even need `bin/drydock lease` in the long run? 67 + $all_blueprints = id(new DrydockBlueprintQuery()) 68 + ->setViewer($viewer) 69 + ->execute(); 70 + $allowed_phids = mpull($all_blueprints, 'getPHID'); 71 + if (!$allowed_phids) { 72 + throw new Exception( 73 + pht( 74 + 'No blueprints exist which can plausibly allocate resources to '. 75 + 'satisfy the requested lease.')); 76 + } 77 + $lease->setAllowedBlueprintPHIDs($allowed_phids); 61 78 62 79 if ($attributes) { 63 80 $lease->setAttributes($attributes);
+8
src/applications/drydock/phid/DrydockBlueprintPHIDType.php
··· 8 8 return pht('Blueprint'); 9 9 } 10 10 11 + public function getPHIDTypeApplicationClass() { 12 + return 'PhabricatorDrydockApplication'; 13 + } 14 + 15 + public function getTypeIcon() { 16 + return 'fa-map-o'; 17 + } 18 + 11 19 public function newObject() { 12 20 return new DrydockBlueprint(); 13 21 }
+8
src/applications/drydock/phid/DrydockLeasePHIDType.php
··· 8 8 return pht('Drydock Lease'); 9 9 } 10 10 11 + public function getPHIDTypeApplicationClass() { 12 + return 'PhabricatorDrydockApplication'; 13 + } 14 + 15 + public function getTypeIcon() { 16 + return 'fa-link'; 17 + } 18 + 11 19 public function newObject() { 12 20 return new DrydockLease(); 13 21 }
+8
src/applications/drydock/phid/DrydockResourcePHIDType.php
··· 8 8 return pht('Drydock Resource'); 9 9 } 10 10 11 + public function getPHIDTypeApplicationClass() { 12 + return 'PhabricatorDrydockApplication'; 13 + } 14 + 15 + public function getTypeIcon() { 16 + return 'fa-map'; 17 + } 18 + 11 19 public function newObject() { 12 20 return new DrydockResource(); 13 21 }
+42 -5
src/applications/drydock/query/DrydockBlueprintQuery.php
··· 7 7 private $blueprintClasses; 8 8 private $datasourceQuery; 9 9 private $disabled; 10 + private $authorizedPHIDs; 10 11 11 12 public function withIDs(array $ids) { 12 13 $this->ids = $ids; ··· 30 31 31 32 public function withDisabled($disabled) { 32 33 $this->disabled = $disabled; 34 + return $this; 35 + } 36 + 37 + public function withAuthorizedPHIDs(array $phids) { 38 + $this->authorizedPHIDs = $phids; 33 39 return $this; 34 40 } 35 41 ··· 37 43 return new DrydockBlueprint(); 38 44 } 39 45 46 + protected function getPrimaryTableAlias() { 47 + return 'blueprint'; 48 + } 49 + 40 50 protected function loadPage() { 41 51 return $this->loadStandardPage($this->newResultObject()); 42 52 } ··· 63 73 if ($this->ids !== null) { 64 74 $where[] = qsprintf( 65 75 $conn, 66 - 'id IN (%Ld)', 76 + 'blueprint.id IN (%Ld)', 67 77 $this->ids); 68 78 } 69 79 70 80 if ($this->phids !== null) { 71 81 $where[] = qsprintf( 72 82 $conn, 73 - 'phid IN (%Ls)', 83 + 'blueprint.phid IN (%Ls)', 74 84 $this->phids); 75 85 } 76 86 77 87 if ($this->datasourceQuery !== null) { 78 88 $where[] = qsprintf( 79 89 $conn, 80 - 'blueprintName LIKE %>', 90 + 'blueprint.blueprintName LIKE %>', 81 91 $this->datasourceQuery); 82 92 } 83 93 84 94 if ($this->blueprintClasses !== null) { 85 95 $where[] = qsprintf( 86 96 $conn, 87 - 'className IN (%Ls)', 97 + 'blueprint.className IN (%Ls)', 88 98 $this->blueprintClasses); 89 99 } 90 100 91 101 if ($this->disabled !== null) { 92 102 $where[] = qsprintf( 93 103 $conn, 94 - 'isDisabled = %d', 104 + 'blueprint.isDisabled = %d', 95 105 (int)$this->disabled); 96 106 } 97 107 98 108 return $where; 109 + } 110 + 111 + protected function shouldGroupQueryResultRows() { 112 + if ($this->authorizedPHIDs !== null) { 113 + return true; 114 + } 115 + return parent::shouldGroupQueryResultRows(); 116 + } 117 + 118 + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { 119 + $joins = parent::buildJoinClauseParts($conn); 120 + 121 + if ($this->authorizedPHIDs !== null) { 122 + $joins[] = qsprintf( 123 + $conn, 124 + 'JOIN %T authorization 125 + ON authorization.blueprintPHID = blueprint.phid 126 + AND authorization.objectPHID IN (%Ls) 127 + AND authorization.objectAuthorizationState = %s 128 + AND authorization.blueprintAuthorizationState = %s', 129 + id(new DrydockAuthorization())->getTableName(), 130 + $this->authorizedPHIDs, 131 + DrydockAuthorization::OBJECTAUTH_ACTIVE, 132 + DrydockAuthorization::BLUEPRINTAUTH_AUTHORIZED); 133 + } 134 + 135 + return $joins; 99 136 } 100 137 101 138 }
+29
src/applications/drydock/storage/DrydockLease.php
··· 7 7 protected $resourceType; 8 8 protected $until; 9 9 protected $ownerPHID; 10 + protected $authorizingPHID; 10 11 protected $attributes = array(); 11 12 protected $status = DrydockLeaseStatus::STATUS_PENDING; 12 13 ··· 139 140 if ($this->getID()) { 140 141 throw new Exception( 141 142 pht('Only new leases may be queued for activation!')); 143 + } 144 + 145 + if (!$this->getAuthorizingPHID()) { 146 + throw new Exception( 147 + pht( 148 + 'Trying to queue a lease for activation without an authorizing '. 149 + 'object. Use "%s" to specify the PHID of the authorizing object. '. 150 + 'The authorizing object must be approved to use the allowed '. 151 + 'blueprints.', 152 + 'setAuthorizingPHID()')); 153 + } 154 + 155 + if (!$this->getAllowedBlueprintPHIDs()) { 156 + throw new Exception( 157 + pht( 158 + 'Trying to queue a lease for activation without any allowed '. 159 + 'Blueprints. Use "%s" to specify allowed blueprints. The '. 160 + 'authorizing object must be approved to use the allowed blueprints.', 161 + 'setAllowedBlueprintPHIDs()')); 142 162 } 143 163 144 164 $this ··· 374 394 public function setAwakenTaskIDs(array $ids) { 375 395 $this->setAttribute('internal.awakenTaskIDs', $ids); 376 396 return $this; 397 + } 398 + 399 + public function setAllowedBlueprintPHIDs(array $phids) { 400 + $this->setAttribute('internal.blueprintPHIDs', $phids); 401 + return $this; 402 + } 403 + 404 + public function getAllowedBlueprintPHIDs() { 405 + return $this->getAttribute('internal.blueprintPHIDs', array()); 377 406 } 378 407 379 408 private function didActivate() {
+38 -3
src/applications/drydock/worker/DrydockLeaseUpdateWorker.php
··· 300 300 return array(); 301 301 } 302 302 303 - $blueprints = id(new DrydockBlueprintQuery()) 303 + $query = id(new DrydockBlueprintQuery()) 304 304 ->setViewer($viewer) 305 305 ->withBlueprintClasses(array_keys($impls)) 306 - ->withDisabled(false) 307 - ->execute(); 306 + ->withDisabled(false); 307 + 308 + $blueprint_phids = $lease->getAllowedBlueprintPHIDs(); 309 + if (!$blueprint_phids) { 310 + $lease->logEvent(DrydockLeaseNoBlueprintsLogType::LOGCONST); 311 + return array(); 312 + } 313 + 314 + // The Drydock application itself is allowed to authorize anything. This 315 + // is primarily used for leases generated by CLI administrative tools. 316 + $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); 317 + 318 + $authorizing_phid = $lease->getAuthorizingPHID(); 319 + if ($authorizing_phid != $drydock_phid) { 320 + $blueprints = id(clone $query) 321 + ->withAuthorizedPHIDs(array($authorizing_phid)) 322 + ->execute(); 323 + if (!$blueprints) { 324 + // If we didn't hit any blueprints, check if this is an authorization 325 + // problem: re-execute the query without the authorization constraint. 326 + // If the second query hits blueprints, the overall configuration is 327 + // fine but this is an authorization problem. If the second query also 328 + // comes up blank, this is some other kind of configuration issue so 329 + // we fall through to the default pathway. 330 + $all_blueprints = $query->execute(); 331 + if ($all_blueprints) { 332 + $lease->logEvent( 333 + DrydockLeaseNoAuthorizationsLogType::LOGCONST, 334 + array( 335 + 'authorizingPHID' => $authorizing_phid, 336 + )); 337 + return array(); 338 + } 339 + } 340 + } else { 341 + $blueprints = $query->execute(); 342 + } 308 343 309 344 $keep = array(); 310 345 foreach ($blueprints as $key => $blueprint) {
-5
src/applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php
··· 67 67 $object->setDetail($key, $value); 68 68 } 69 69 70 - public function applyApplicationTransactionExternalEffects( 71 - PhabricatorApplicationTransaction $xaction) { 72 - return; 73 - } 74 - 75 70 public function getBuildTargetFieldValue() { 76 71 return $this->getProxy()->getFieldValue(); 77 72 }
+5 -1
src/applications/harbormaster/phid/HarbormasterBuildStepPHIDType.php
··· 28 28 foreach ($handles as $phid => $handle) { 29 29 $build_step = $objects[$phid]; 30 30 31 + $id = $build_step->getID(); 31 32 $name = $build_step->getName(); 32 33 33 - $handle->setName($name); 34 + $handle 35 + ->setName($name) 36 + ->setFullName(pht('Build Step %d: %s', $id, $name)) 37 + ->setURI("/harbormaster/step/{$id}/edit/"); 34 38 } 35 39 } 36 40
+11 -1
src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php
··· 41 41 $working_copy_type = id(new DrydockWorkingCopyBlueprintImplementation()) 42 42 ->getType(); 43 43 44 + $allowed_phids = $build_target->getFieldValue('repositoryPHIDs'); 45 + $authorizing_phid = $build_target->getBuildStep()->getPHID(); 46 + 44 47 $lease = DrydockLease::initializeNewLease() 45 48 ->setResourceType($working_copy_type) 46 - ->setOwnerPHID($build_target->getPHID()); 49 + ->setOwnerPHID($build_target->getPHID()) 50 + ->setAuthorizingPHID($authorizing_phid) 51 + ->setAllowedBlueprintPHIDs($allowed_phids); 47 52 48 53 $map = $this->buildRepositoryMap($build_target); 49 54 ··· 102 107 'name' => array( 103 108 'name' => pht('Artifact Name'), 104 109 'type' => 'text', 110 + 'required' => true, 111 + ), 112 + 'blueprintPHIDs' => array( 113 + 'name' => pht('Use Blueprints'), 114 + 'type' => 'blueprints', 105 115 'required' => true, 106 116 ), 107 117 'repositoryPHIDs' => array(