diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php index bc8c7e8539..5b794d3479 100644 --- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php @@ -1,454 +1,455 @@ setViewer(PhabricatorUser::getOmnipotentUser()) ->withIDs(array($lease_id)) - ->needResources(true) ->execute(); $lease = idx($query, $lease_id); if (!$lease) { throw new Exception("No such lease '{$lease_id}'!"); } return $lease; } protected function getInstance() { if (!$this->instance) { throw new Exception( "Attach the blueprint instance to the implementation."); } return $this->instance; } public function attachInstance(DrydockBlueprint $instance) { $this->instance = $instance; return $this; } /* -( Lease Acquisition )-------------------------------------------------- */ /** * @task lease */ final public function filterResource( DrydockResource $resource, DrydockLease $lease) { $scope = $this->pushActiveScope($resource, $lease); return $this->canAllocateLease($resource, $lease); } /** * Enforce basic checks on lease/resource compatibility. Allows resources to * reject leases if they are incompatible, even if the resource types match. * * For example, if a resource represents a 32-bit host, this method might * reject leases that need a 64-bit host. If a resource represents a working * copy of repository "X", this method might reject leases which need a * working copy of repository "Y". Generally, although the main types of * a lease and resource may match (e.g., both "host"), it may not actually be * possible to satisfy the lease with a specific resource. * * This method generally should not enforce limits or perform capacity * checks. Perform those in @{method:shouldAllocateLease} instead. It also * should not perform actual acquisition of the lease; perform that in * @{method:executeAcquireLease} instead. * * @param DrydockResource Candidiate resource to allocate the lease on. * @param DrydockLease Pending lease that wants to allocate here. * @return bool True if the resource and lease are compatible. * @task lease */ abstract protected function canAllocateLease( DrydockResource $resource, DrydockLease $lease); /** * @task lease */ final public function allocateLease( DrydockResource $resource, DrydockLease $lease) { $scope = $this->pushActiveScope($resource, $lease); $this->log('Trying to Allocate Lease'); $lease->setStatus(DrydockLeaseStatus::STATUS_ACQUIRING); $lease->setResourceID($resource->getID()); $lease->attachResource($resource); $ephemeral_lease = id(clone $lease)->makeEphemeral(); $allocated = false; $allocation_exception = null; $resource->openTransaction(); $resource->beginReadLocking(); $resource->reload(); $other_leases = id(new DrydockLease())->loadAllWhere( 'status IN (%Ld) AND resourceID = %d', array( DrydockLeaseStatus::STATUS_ACQUIRING, DrydockLeaseStatus::STATUS_ACTIVE, ), $resource->getID()); try { $allocated = $this->shouldAllocateLease( $resource, $ephemeral_lease, $other_leases); } catch (Exception $ex) { $allocation_exception = $ex; } if ($allocated) { $lease->save(); } $resource->endReadLocking(); if ($allocated) { $resource->saveTransaction(); $this->log('Allocated Lease'); } else { $resource->killTransaction(); $this->log('Failed to Allocate Lease'); } if ($allocation_exception) { $this->logException($allocation_exception); } return $allocated; } /** * Enforce lease limits on resources. Allows resources to reject leases if * they would become over-allocated by accepting them. * * For example, if a resource represents disk space, this method might check * how much space the lease is asking for (say, 200MB) and how much space is * left unallocated on the resource. It could grant the lease (return true) * if it has enough remaining space (more than 200MB), and reject the lease * (return false) if it does not (less than 200MB). * * A resource might also allow only exclusive leases. In this case it could * accept a new lease (return true) if there are no active leases, or reject * the new lease (return false) if there any other leases. * * A lock is held on the resource while this method executes to prevent * multiple processes from allocating leases on the resource simultaneously. * However, this means you should implement the method as cheaply as possible. * In particular, do not perform any actual acquisition or setup in this * method. * * If allocation is permitted, the lease will be moved to `ACQUIRING` status * and @{method:executeAcquireLease} will be called to actually perform * acquisition. * * General compatibility checks unrelated to resource limits and capacity are * better implemented in @{method:canAllocateLease}, which serves as a * cheap filter before lock acquisition. * * @param DrydockResource Candidate resource to allocate the lease on. * @param DrydockLease Pending lease that wants to allocate here. * @param list Other allocated and acquired leases on the * resource. The implementation can inspect them * to verify it can safely add the new lease. * @return bool True to allocate the lease on the resource; * false to reject it. * @task lease */ abstract protected function shouldAllocateLease( DrydockResource $resource, DrydockLease $lease, array $other_leases); /** * @task lease */ final public function acquireLease( DrydockResource $resource, DrydockLease $lease) { $scope = $this->pushActiveScope($resource, $lease); $this->log('Acquiring Lease'); $lease->setStatus(DrydockLeaseStatus::STATUS_ACTIVE); $lease->setResourceID($resource->getID()); $lease->attachResource($resource); $ephemeral_lease = id(clone $lease)->makeEphemeral(); try { $this->executeAcquireLease($resource, $ephemeral_lease); } catch (Exception $ex) { $this->logException($ex); throw $ex; } $lease->setAttributes($ephemeral_lease->getAttributes()); $lease->save(); $this->log('Acquired Lease'); } /** * Acquire and activate an allocated lease. Allows resources to peform setup * as leases are brought online. * * Following a successful call to @{method:canAllocateLease}, a lease is moved * to `ACQUIRING` status and this method is called after resource locks are * released. Nothing is locked while this method executes; the implementation * is free to perform expensive operations like writing files and directories, * executing commands, etc. * * After this method executes, the lease status is moved to `ACTIVE` and the * original leasee may access it. * * If acquisition fails, throw an exception. * * @param DrydockResource Resource to acquire a lease on. * @param DrydockLease Lease to acquire. * @return void */ abstract protected function executeAcquireLease( DrydockResource $resource, DrydockLease $lease); final public function releaseLease( DrydockResource $resource, DrydockLease $lease) { $scope = $this->pushActiveScope(null, $lease); $released = false; $lease->openTransaction(); $lease->beginReadLocking(); $lease->reload(); if ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACTIVE) { $lease->setStatus(DrydockLeaseStatus::STATUS_RELEASED); $lease->save(); $released = true; } $lease->endReadLocking(); $lease->saveTransaction(); if (!$released) { throw new Exception("Unable to release lease: lease not active!"); } } /* -( Resource Allocation )------------------------------------------------ */ public function canAllocateMoreResources(array $pool) { return true; } abstract protected function executeAllocateResource(DrydockLease $lease); final public function allocateResource(DrydockLease $lease) { $scope = $this->pushActiveScope(null, $lease); $this->log( pht( "Blueprint '%s': Allocating Resource for '%s'", $this->getBlueprintClass(), $lease->getLeaseName())); try { $resource = $this->executeAllocateResource($lease); $this->validateAllocatedResource($resource); } catch (Exception $ex) { $this->logException($ex); throw $ex; } return $resource; } /* -( Logging )------------------------------------------------------------ */ /** * @task log */ protected function logException(Exception $ex) { $this->log($ex->getMessage()); } /** * @task log */ protected function log($message) { self::writeLog( $this->activeResource, $this->activeLease, $message); } /** * @task log */ public static function writeLog( DrydockResource $resource = null, DrydockLease $lease = null, $message) { $log = id(new DrydockLog()) ->setEpoch(time()) ->setMessage($message); if ($resource) { $log->setResourceID($resource->getID()); } if ($lease) { $log->setLeaseID($lease->getID()); } $log->save(); } public static function getAllBlueprintImplementations() { static $list = null; if ($list === null) { $blueprints = id(new PhutilSymbolLoader()) ->setType('class') ->setAncestorClass('DrydockBlueprintImplementation') ->setConcreteOnly(true) ->selectAndLoadSymbols(); $list = ipull($blueprints, 'name', 'name'); foreach ($list as $class_name => $ignored) { $list[$class_name] = newv($class_name, array()); } } return $list; } public static function getAllBlueprintImplementationsForResource($type) { static $groups = null; if ($groups === null) { $groups = mgroup(self::getAllBlueprintImplementations(), 'getType'); } return idx($groups, $type, array()); } protected function newResourceTemplate($name) { $resource = new DrydockResource(); $resource->setBlueprintPHID($this->getInstance()->getPHID()); $resource->setBlueprintClass($this->getBlueprintClass()); $resource->setType($this->getType()); $resource->setStatus(DrydockResourceStatus::STATUS_PENDING); $resource->setName($name); $resource->save(); $this->activeResource = $resource; $this->log( pht( "Blueprint '%s': Created New Template", $this->getBlueprintClass())); return $resource; } /** * Sanity checks that the blueprint is implemented properly. */ private function validateAllocatedResource($resource) { $blueprint = $this->getBlueprintClass(); if (!($resource instanceof DrydockResource)) { throw new Exception( "Blueprint '{$blueprint}' is not properly implemented: ". "executeAllocateResource() must return an object of type ". "DrydockResource or throw, but returned something else."); } $current_status = $resource->getStatus(); $req_status = DrydockResourceStatus::STATUS_OPEN; if ($current_status != $req_status) { $current_name = DrydockResourceStatus::getNameForStatus($current_status); $req_name = DrydockResourceStatus::getNameForStatus($req_status); throw new Exception( "Blueprint '{$blueprint}' is not properly implemented: ". "executeAllocateResource() must return a DrydockResource with ". "status '{$req_name}', but returned one with status ". "'{$current_name}'."); } } private function pushActiveScope( DrydockResource $resource = null, DrydockLease $lease = null) { if (($this->activeResource !== null) || ($this->activeLease !== null)) { throw new Exception("There is already an active resource or lease!"); } $this->activeResource = $resource; $this->activeLease = $lease; return new DrydockBlueprintScopeGuard($this); } public function popActiveScope() { $this->activeResource = null; $this->activeLease = null; } } diff --git a/src/applications/drydock/controller/DrydockLeaseListController.php b/src/applications/drydock/controller/DrydockLeaseListController.php index 791299ff4f..b168f9bc7c 100644 --- a/src/applications/drydock/controller/DrydockLeaseListController.php +++ b/src/applications/drydock/controller/DrydockLeaseListController.php @@ -1,46 +1,46 @@ getRequest(); $user = $request->getUser(); $nav = $this->buildSideNav('lease'); $pager = new AphrontPagerView(); $pager->setURI(new PhutilURI('/drydock/lease/'), 'offset'); $pager->setOffset($request->getInt('offset')); $leases = id(new DrydockLeaseQuery()) - ->needResources(true) + ->setViewer($user) ->executeWithOffsetPager($pager); $title = pht('Leases'); $header = id(new PHUIHeaderView()) ->setHeader($title); $lease_list = $this->buildLeaseListView($leases); $nav->appendChild( array( $header, $lease_list, $pager, )); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title, $request->getRequestURI()); $nav->setCrumbs($crumbs); return $this->buildApplicationPage( $nav, array( 'device' => true, 'title' => $title, )); } } diff --git a/src/applications/drydock/controller/DrydockLeaseReleaseController.php b/src/applications/drydock/controller/DrydockLeaseReleaseController.php index dd238e2467..f3cc501569 100644 --- a/src/applications/drydock/controller/DrydockLeaseReleaseController.php +++ b/src/applications/drydock/controller/DrydockLeaseReleaseController.php @@ -1,55 +1,58 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); - $lease = id(new DrydockLease())->load($this->id); + $lease = id(new DrydockLeaseQuery()) + ->setViewer($user) + ->withIDs(array($this->id)) + ->executeOne(); if (!$lease) { return new Aphront404Response(); } $lease_uri = '/lease/'.$lease->getID().'/'; $lease_uri = $this->getApplicationURI($lease_uri); if ($lease->getStatus() != DrydockLeaseStatus::STATUS_ACTIVE) { $dialog = id(new AphrontDialogView()) ->setUser($user) ->setTitle(pht('Lease Not Active')) ->appendChild(phutil_tag('p', array(), pht( 'You can only release "active" leases.'))) ->addCancelButton($lease_uri); return id(new AphrontDialogResponse())->setDialog($dialog); } if (!$request->isDialogFormPost()) { $dialog = id(new AphrontDialogView()) ->setUser($user) ->setTitle(pht('Really release lease?')) ->appendChild(phutil_tag('p', array(), pht( 'Releasing a lease may cause trouble for the lease holder and '. 'trigger cleanup of the underlying resource. It can not be '. 'undone. Continue?'))) ->addSubmitButton(pht('Release Lease')) ->addCancelButton($lease_uri); return id(new AphrontDialogResponse())->setDialog($dialog); } - $resource = $lease->loadResource(); + $resource = $lease->getResource(); $blueprint = $resource->getBlueprint(); $blueprint->releaseLease($resource, $lease); return id(new AphrontReloadResponse())->setURI($lease_uri); } } diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php index 46ecc1a4c4..459d0160bf 100644 --- a/src/applications/drydock/controller/DrydockResourceViewController.php +++ b/src/applications/drydock/controller/DrydockResourceViewController.php @@ -1,127 +1,127 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $resource = id(new DrydockResource())->load($this->id); if (!$resource) { return new Aphront404Response(); } $title = 'Resource '.$resource->getID().' '.$resource->getName(); $header = id(new PHUIHeaderView()) ->setHeader($title); $actions = $this->buildActionListView($resource); $properties = $this->buildPropertyListView($resource, $actions); $resource_uri = 'resource/'.$resource->getID().'/'; $resource_uri = $this->getApplicationURI($resource_uri); $leases = id(new DrydockLeaseQuery()) + ->setViewer($user) ->withResourceIDs(array($resource->getID())) - ->needResources(true) ->execute(); $lease_list = $this->buildLeaseListView($leases); $lease_list->setNoDataString(pht('This resource has no leases.')); $pager = new AphrontPagerView(); $pager->setURI(new PhutilURI($resource_uri), 'offset'); $pager->setOffset($request->getInt('offset')); $logs = id(new DrydockLogQuery()) ->withResourceIDs(array($resource->getID())) ->executeWithOffsetPager($pager); $log_table = $this->buildLogTableView($logs); $log_table->appendChild($pager); $crumbs = $this->buildApplicationCrumbs(); $crumbs->setActionList($actions); $crumbs->addTextCrumb(pht('Resource %d', $resource->getID())); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); return $this->buildApplicationPage( array( $crumbs, $object_box, $lease_list, $log_table, ), array( 'device' => true, 'title' => $title, )); } private function buildActionListView(DrydockResource $resource) { $view = id(new PhabricatorActionListView()) ->setUser($this->getRequest()->getUser()) ->setObjectURI($this->getRequest()->getRequestURI()) ->setObject($resource); $can_close = ($resource->getStatus() == DrydockResourceStatus::STATUS_OPEN); $uri = '/resource/'.$resource->getID().'/close/'; $uri = $this->getApplicationURI($uri); $view->addAction( id(new PhabricatorActionView()) ->setHref($uri) ->setName(pht('Close Resource')) ->setIcon('delete') ->setWorkflow(true) ->setDisabled(!$can_close)); return $view; } private function buildPropertyListView( DrydockResource $resource, PhabricatorActionListView $actions) { $view = new PHUIPropertyListView(); $view->setActionList($actions); $status = $resource->getStatus(); $status = DrydockResourceStatus::getNameForStatus($status); $view->addProperty( pht('Status'), $status); $view->addProperty( pht('Resource Type'), $resource->getType()); // TODO: Load handle. $view->addProperty( pht('Blueprint'), $resource->getBlueprintPHID()); $attributes = $resource->getAttributes(); if ($attributes) { $view->addSectionHeader(pht('Attributes')); foreach ($attributes as $key => $value) { $view->addProperty($key, $value); } } return $view; } } diff --git a/src/applications/drydock/management/DrydockManagementReleaseWorkflow.php b/src/applications/drydock/management/DrydockManagementReleaseWorkflow.php index 54b98bf62d..5f482786ad 100644 --- a/src/applications/drydock/management/DrydockManagementReleaseWorkflow.php +++ b/src/applications/drydock/management/DrydockManagementReleaseWorkflow.php @@ -1,45 +1,52 @@ setName('release') ->setSynopsis('Release a lease.') ->setArguments( array( array( 'name' => 'ids', 'wildcard' => true, ), )); } public function execute(PhutilArgumentParser $args) { $console = PhutilConsole::getConsole(); $ids = $args->getArg('ids'); if (!$ids) { throw new PhutilArgumentUsageException( "Specify one or more lease IDs to release."); } + $viewer = PhabricatorUser::getOmnipotentUser(); + + $leases = id(new DrydockLeaseQuery()) + ->setViewer($viewer) + ->withIDs($ids) + ->execute(); + foreach ($ids as $id) { - $lease = id(new DrydockLease())->load($id); + $lease = idx($leases, $id); if (!$lease) { $console->writeErr("Lease %d does not exist!\n", $id); } else if ($lease->getStatus() != DrydockLeaseStatus::STATUS_ACTIVE) { $console->writeErr("Lease %d is not 'active'!\n", $id); } else { - $resource = $lease->loadResource(); + $resource = $lease->getResource(); $blueprint = $resource->getBlueprint(); $blueprint->releaseLease($resource, $lease); $console->writeErr("Released lease %d.\n", $id); } } } } diff --git a/src/applications/drydock/query/DrydockLeaseQuery.php b/src/applications/drydock/query/DrydockLeaseQuery.php index 9b67acd363..05cd40c49a 100644 --- a/src/applications/drydock/query/DrydockLeaseQuery.php +++ b/src/applications/drydock/query/DrydockLeaseQuery.php @@ -1,84 +1,79 @@ resourceIDs = $ids; - return $this; - } public function withIDs(array $ids) { $this->ids = $ids; return $this; } - public function needResources($need_resources) { - $this->needResources = $need_resources; + public function withResourceIDs(array $ids) { + $this->resourceIDs = $ids; return $this; } - public function execute() { + public function loadPage() { $table = new DrydockLease(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT lease.* FROM %T lease %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); - $leases = $table->loadAllFromArray($data); + return $table->loadAllFromArray($data); + } - if ($leases && $this->needResources) { - $resources = id(new DrydockResource())->loadAllWhere( - 'id IN (%Ld)', - mpull($leases, 'getResourceID')); - - foreach ($leases as $key => $lease) { - if ($lease->getResourceID()) { - $resource = idx($resources, $lease->getResourceID()); - if ($resource) { - $lease->attachResource($resource); - } else { - unset($leases[$key]); - } - } else { - unset($leases[$key]); - } + public function willFilterPage(array $leases) { + $resources = id(new DrydockResourceQuery()) + ->setParentQuery($this) + ->setViewer($this->getViewer()) + ->withIDs(mpull($leases, 'getResourceID')) + ->execute(); + + foreach ($leases as $key => $lease) { + $resource = idx($resources, $lease->getResourceID()); + if (!$resource) { + unset($leases[$key]); + continue; } + $lease->attachResource($resource); } return $leases; } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->resourceIDs) { $where[] = qsprintf( $conn_r, 'resourceID IN (%Ld)', $this->resourceIDs); } if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } + $where[] = $this->buildPagingClause($conn_r); + return $this->formatWhereClause($where); } - private function buildOrderClause(AphrontDatabaseConnection $conn_r) { - return qsprintf($conn_r, 'ORDER BY id DESC'); + public function getQueryApplicationClass() { + return 'PhabricatorApplicationDrydock'; } } diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index 9e08dbd3b1..fdaac35e9f 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -1,190 +1,213 @@ releaseOnDestruction = true; return $this; } public function __destruct() { if ($this->releaseOnDestruction) { if ($this->isActive()) { $this->release(); } } } public function getLeaseName() { return pht('Lease %d', $this->getID()); } public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'attributes' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } public function setAttribute($key, $value) { $this->attributes[$key] = $value; return $this; } public function getAttribute($key, $default = null) { return idx($this->attributes, $key, $default); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorPHIDConstants::PHID_TYPE_DRYL); } public function getInterface($type) { return $this->getResource()->getInterface($this, $type); } public function getResource() { return $this->assertAttached($this->resource); } public function attachResource(DrydockResource $resource) { $this->resource = $resource; return $this; } public function hasAttachedResource() { return ($this->resource !== null); } public function loadResource() { return id(new DrydockResource())->loadOneWhere( 'id = %d', $this->getResourceID()); } public function queueForActivation() { if ($this->getID()) { throw new Exception( "Only new leases may be queued for activation!"); } $this->setStatus(DrydockLeaseStatus::STATUS_PENDING); $this->save(); // NOTE: Prevent a race where some eager worker quickly grabs the task // before we can save the Task ID. $this->openTransaction(); $this->beginReadLocking(); $this->reload(); $task = PhabricatorWorker::scheduleTask( 'DrydockAllocatorWorker', $this->getID()); $this->setTaskID($task->getID()); $this->save(); $this->endReadLocking(); $this->saveTransaction(); return $this; } public function release() { $this->assertActive(); $this->setStatus(DrydockLeaseStatus::STATUS_RELEASED); $this->save(); $this->resource = null; return $this; } public function isActive() { switch ($this->status) { case DrydockLeaseStatus::STATUS_ACTIVE: case DrydockLeaseStatus::STATUS_ACQUIRING: return true; } return false; } private function assertActive() { if (!$this->isActive()) { throw new Exception( "Lease is not active! You can not interact with resources through ". "an inactive lease."); } } public static function waitForLeases(array $leases) { assert_instances_of($leases, 'DrydockLease'); $task_ids = array_filter(mpull($leases, 'getTaskID')); PhabricatorWorker::waitForTasks($task_ids); $unresolved = $leases; while (true) { foreach ($unresolved as $key => $lease) { $lease->reload(); switch ($lease->getStatus()) { case DrydockLeaseStatus::STATUS_ACTIVE: unset($unresolved[$key]); break; case DrydockLeaseStatus::STATUS_RELEASED: throw new Exception("Lease has already been released!"); case DrydockLeaseStatus::STATUS_EXPIRED: throw new Exception("Lease has already expired!"); case DrydockLeaseStatus::STATUS_BROKEN: throw new Exception("Lease has been broken!"); case DrydockLeaseStatus::STATUS_PENDING: case DrydockLeaseStatus::STATUS_ACQUIRING: break; } } if ($unresolved) { sleep(1); } else { break; } } foreach ($leases as $lease) { $lease->attachResource($lease->loadResource()); } } public function waitUntilActive() { if (!$this->getID()) { $this->queueForActivation(); } self::waitForLeases(array($this)); return $this; } + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + return $this->getResource()->getPolicy($capability); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return $this->getResource()->hasAutomaticCapability($capability, $viewer); + } + + public function describeAutomaticCapability($capability) { + return pht('Leases inherit policies from the resources they lease.'); + } + } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php b/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php index 56bf746b5d..0511c526a9 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php @@ -1,145 +1,146 @@ setBuildTargetPHID($build_target->getPHID()); } public function getConfiguration() { return array( self::CONFIG_SERIALIZATION => array( 'artifactData' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } public function attachBuildTarget(HarbormasterBuildTarget $build_target) { $this->buildTarget = $build_target; return $this; } public function getBuildTarget() { return $this->assertAttached($this->buildTarget); } public function setArtifactKey($build_phid, $key) { $this->artifactIndex = PhabricatorHash::digestForIndex($build_phid.$key); $this->artifactKey = $key; return $this; } public function getObjectItemView(PhabricatorUser $viewer) { $data = $this->getArtifactData(); switch ($this->getArtifactType()) { case self::TYPE_FILE: $handle = id(new PhabricatorHandleQuery()) ->setViewer($viewer) ->withPHIDs($data) ->executeOne(); return id(new PHUIObjectItemView()) ->setObjectName(pht('File')) ->setHeader($handle->getFullName()) ->setHref($handle->getURI()); case self::TYPE_HOST: $leases = id(new DrydockLeaseQuery()) + ->setViewer($viewer) ->withIDs(array($data["drydock-lease"])) ->execute(); $lease = $leases[$data["drydock-lease"]]; return id(new PHUIObjectItemView()) ->setObjectName(pht('Drydock Lease')) ->setHeader($lease->getID()) ->setHref('/drydock/lease/'.$lease->getID()); default: return null; } } public function loadDrydockLease() { if ($this->getArtifactType() !== self::TYPE_HOST) { throw new Exception( "`loadDrydockLease` may only be called on host artifacts."); } $data = $this->getArtifactData(); // FIXME: Is there a better way of doing this? $lease = id(new DrydockLease())->load( $data['drydock-lease']); if ($lease === null) { throw new Exception("Associated Drydock lease not found!"); } $resource = id(new DrydockResource())->load( $lease->getResourceID()); if ($resource === null) { throw new Exception("Associated Drydock resource not found!"); } $lease->attachResource($resource); return $lease; } public function loadPhabricatorFile() { if ($this->getArtifactType() !== self::TYPE_FILE) { throw new Exception( "`loadPhabricatorFile` may only be called on file artifacts."); } $data = $this->getArtifactData(); // The data for TYPE_FILE is an array with a single PHID in it. $phid = $data["filePHID"]; $file = id(new PhabricatorFileQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withPHIDs(array($phid)) ->executeOne(); if ($file === null) { throw new Exception("Associated file not found!"); } return $file; } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, ); } public function getPolicy($capability) { return $this->getBuildTarget()->getPolicy($capability); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return $this->getBuildTarget()->hasAutomaticCapability( $capability, $viewer); } public function describeAutomaticCapability($capability) { return pht( 'Users must be able to see a buildable to see its artifacts.'); } }