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

Migrate "Run Command" to use Drydock hosts

Summary: This migrates the "Run Remote Command" build step over to use Drydock hosts and Harbormaster artifacts.

Test Plan:
Created a build plan with a "Lease Host" step and a "Run Command" step. Configured the "Run Command" step to use the artifact from the "Lease Host" step.

Saw the results:

{F87377}

{F87378}

Reviewers: epriestley, #blessed_reviewers

Reviewed By: epriestley

CC: Korvin, epriestley, aran

Maniphest Tasks: T1049, T4111

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

+256 -197
+2 -2
src/__phutil_library_map__.php
··· 102 102 'CelerityResourceTransformerTestCase' => 'infrastructure/celerity/__tests__/CelerityResourceTransformerTestCase.php', 103 103 'CeleritySpriteGenerator' => 'infrastructure/celerity/CeleritySpriteGenerator.php', 104 104 'CelerityStaticResourceResponse' => 'infrastructure/celerity/CelerityStaticResourceResponse.php', 105 + 'CommandBuildStepImplementation' => 'applications/harbormaster/step/CommandBuildStepImplementation.php', 105 106 'ConduitAPIMethod' => 'applications/conduit/method/ConduitAPIMethod.php', 106 107 'ConduitAPIRequest' => 'applications/conduit/protocol/ConduitAPIRequest.php', 107 108 'ConduitAPIResponse' => 'applications/conduit/protocol/ConduitAPIResponse.php', ··· 2322 2323 'ReleephStatusFieldSpecification' => 'applications/releeph/field/specification/ReleephStatusFieldSpecification.php', 2323 2324 'ReleephSummaryFieldSpecification' => 'applications/releeph/field/specification/ReleephSummaryFieldSpecification.php', 2324 2325 'ReleephUserView' => 'applications/releeph/view/user/ReleephUserView.php', 2325 - 'RemoteCommandBuildStepImplementation' => 'applications/harbormaster/step/RemoteCommandBuildStepImplementation.php', 2326 2326 'ShellLogView' => 'applications/harbormaster/view/ShellLogView.php', 2327 2327 'SleepBuildStepImplementation' => 'applications/harbormaster/step/SleepBuildStepImplementation.php', 2328 2328 'SlowvoteEmbedView' => 'applications/slowvote/view/SlowvoteEmbedView.php', ··· 2442 2442 'CelerityResourceController' => 'PhabricatorController', 2443 2443 'CelerityResourceGraph' => 'AbstractDirectedGraph', 2444 2444 'CelerityResourceTransformerTestCase' => 'PhabricatorTestCase', 2445 + 'CommandBuildStepImplementation' => 'VariableBuildStepImplementation', 2445 2446 'ConduitAPIMethod' => 2446 2447 array( 2447 2448 0 => 'Phobject', ··· 4950 4951 'ReleephStatusFieldSpecification' => 'ReleephFieldSpecification', 4951 4952 'ReleephSummaryFieldSpecification' => 'ReleephFieldSpecification', 4952 4953 'ReleephUserView' => 'AphrontView', 4953 - 'RemoteCommandBuildStepImplementation' => 'VariableBuildStepImplementation', 4954 4954 'ShellLogView' => 'AphrontView', 4955 4955 'SleepBuildStepImplementation' => 'BuildStepImplementation', 4956 4956 'SlowvoteEmbedView' => 'AphrontView',
+1 -18
src/applications/harbormaster/config/PhabricatorHarbormasterConfigOptions.php
··· 12 12 } 13 13 14 14 public function getOptions() { 15 - return array( 16 - $this->newOption( 17 - 'harbormaster.temporary.hosts.whitelist', 18 - 'list<string>', 19 - array()) 20 - ->setSummary('Temporary configuration value.') 21 - ->setLocked(true) 22 - ->setDescription( 23 - pht( 24 - "This specifies a whitelist of remote hosts that the \"Run ". 25 - "Remote Command\" may connect to. This is a temporary ". 26 - "configuration option as Drydock is not yet available.". 27 - "\n\n". 28 - "**This configuration option will be removed in the future and ". 29 - "your build configuration will no longer work when Drydock ". 30 - "replaces this option. There is ABSOLUTELY NO SUPPORT for ". 31 - "using this functionality!**")) 32 - ); 15 + return array(); 33 16 } 34 17 35 18 }
+30 -1
src/applications/harbormaster/controller/HarbormasterBuildViewController.php
··· 73 73 ->setHeader($header) 74 74 ->addPropertyList($properties); 75 75 76 + $targets[] = $this->buildArtifacts($build_target); 76 77 $targets[] = $this->buildLog($build, $build_target); 77 78 } 78 - 79 79 80 80 return $this->buildApplicationPage( 81 81 array( ··· 87 87 'title' => $title, 88 88 'device' => true, 89 89 )); 90 + } 91 + 92 + private function buildArtifacts(HarbormasterBuildTarget $build_target) { 93 + $request = $this->getRequest(); 94 + $viewer = $request->getUser(); 95 + 96 + $artifacts = id(new HarbormasterBuildArtifactQuery()) 97 + ->setViewer($viewer) 98 + ->withBuildTargetPHIDs(array($build_target->getPHID())) 99 + ->execute(); 100 + 101 + if (count($artifacts) === 0) { 102 + return null; 103 + } 104 + 105 + $list = new PHUIObjectItemListView(); 106 + 107 + foreach ($artifacts as $artifact) { 108 + $list->addItem($artifact->getObjectItemView($viewer)); 109 + } 110 + 111 + $header = id(new PHUIHeaderView()) 112 + ->setHeader(pht('Build Artifacts')) 113 + ->setUser($viewer); 114 + 115 + $box = id(new PHUIObjectBoxView()) 116 + ->setHeader($header); 117 + 118 + return array($box, $list); 90 119 } 91 120 92 121 private function buildLog(
+18
src/applications/harbormaster/controller/HarbormasterStepEditController.php
··· 91 91 ->setName($name) 92 92 ->setValue($value); 93 93 break; 94 + case BuildStepImplementation::SETTING_TYPE_ARTIFACT: 95 + $filter = $opt['artifact_type']; 96 + $available_artifacts = 97 + BuildStepImplementation::getAvailableArtifacts( 98 + $plan, 99 + $step, 100 + $filter); 101 + $options = array(); 102 + foreach ($available_artifacts as $key => $type) { 103 + $options[$key] = $key; 104 + } 105 + $control = id(new AphrontFormSelectControl()) 106 + ->setLabel($this->getReadableName($name, $opt)) 107 + ->setName($name) 108 + ->setValue($value) 109 + ->setOptions($options); 110 + break; 94 111 default: 95 112 throw new Exception("Unable to render field with unknown type."); 96 113 } ··· 145 162 public function getValueFromRequest(AphrontRequest $request, $name, $type) { 146 163 switch ($type) { 147 164 case BuildStepImplementation::SETTING_TYPE_STRING: 165 + case BuildStepImplementation::SETTING_TYPE_ARTIFACT: 148 166 return $request->getStr($name); 149 167 break; 150 168 case BuildStepImplementation::SETTING_TYPE_INTEGER:
+5 -2
src/applications/harbormaster/query/HarbormasterBuildArtifactQuery.php
··· 7 7 private $buildTargetPHIDs; 8 8 private $artifactTypes; 9 9 private $artifactKeys; 10 + private $keyBuildPHID; 10 11 11 12 public function withIDs(array $ids) { 12 13 $this->ids = $ids; ··· 23 24 return $this; 24 25 } 25 26 26 - public function withArtifactKeys(array $artifact_keys) { 27 + public function withArtifactKeys($build_phid, array $artifact_keys) { 28 + $this->keyBuildPHID = $build_phid; 27 29 $this->artifactKeys = $artifact_keys; 28 30 return $this; 29 31 } ··· 95 97 if ($this->artifactKeys) { 96 98 $indexes = array(); 97 99 foreach ($this->artifactKeys as $key) { 98 - $indexes[] = PhabricatorHash::digestForIndex($key); 100 + $indexes[] = PhabricatorHash::digestForIndex( 101 + $this->keyBuildPHID.$key); 99 102 } 100 103 101 104 $where[] = qsprintf(
+5 -9
src/applications/harbormaster/step/BuildStepImplementation.php
··· 7 7 const SETTING_TYPE_STRING = 'string'; 8 8 const SETTING_TYPE_INTEGER = 'integer'; 9 9 const SETTING_TYPE_BOOLEAN = 'boolean'; 10 + const SETTING_TYPE_ARTIFACT = 'artifact'; 10 11 11 12 public static function getImplementations() { 12 13 $symbols = id(new PhutilSymbolLoader()) ··· 117 118 * Returns a list of all artifacts made available by previous build steps. 118 119 */ 119 120 public static function getAvailableArtifacts( 121 + HarbormasterBuildPlan $build_plan, 120 122 HarbormasterBuildStep $current_build_step, 121 123 $artifact_type) { 122 - 123 - $build_plan_phid = $current_build_step->getBuildPlanPHID(); 124 - 125 - $build_plan = id(new HarbormasterBuildPlanQuery()) 126 - ->withPHIDs(array($build_plan_phid)) 127 - ->executeOne(); 128 124 129 125 $build_steps = $build_plan->loadOrderedBuildSteps(); 130 126 131 127 $previous_implementations = array(); 132 - for ($i = 0; $i < count($build_steps); $i++) { 133 - if ($build_steps[$i]->getPHID() === $current_build_step->getPHID()) { 128 + foreach ($build_steps as $build_step) { 129 + if ($build_step->getPHID() === $current_build_step->getPHID()) { 134 130 break; 135 131 } 136 - $previous_implementations[$i] = $build_steps[$i]->getStepImplementation(); 132 + $previous_implementations[] = $build_step->getStepImplementation(); 137 133 } 138 134 139 135 $artifact_arrays = mpull($previous_implementations, 'getArtifactMappings');
+139
src/applications/harbormaster/step/CommandBuildStepImplementation.php
··· 1 + <?php 2 + 3 + final class CommandBuildStepImplementation 4 + extends VariableBuildStepImplementation { 5 + 6 + public function getName() { 7 + return pht('Run Command'); 8 + } 9 + 10 + public function getGenericDescription() { 11 + return pht('Run a command on Drydock host.'); 12 + } 13 + 14 + public function getDescription() { 15 + $settings = $this->getSettings(); 16 + 17 + return pht( 18 + 'Run \'%s\' on \'%s\'.', 19 + $settings['command'], 20 + $settings['hostartifact']); 21 + } 22 + 23 + public function execute( 24 + HarbormasterBuild $build, 25 + HarbormasterBuildTarget $build_target) { 26 + 27 + $settings = $this->getSettings(); 28 + $variables = $build_target->getVariables(); 29 + 30 + $command = $this->mergeVariables( 31 + 'vcsprintf', 32 + $settings['command'], 33 + $variables); 34 + 35 + $artifact = id(new HarbormasterBuildArtifactQuery()) 36 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 37 + ->withArtifactKeys( 38 + $build->getPHID(), 39 + array($settings['hostartifact'])) 40 + ->executeOne(); 41 + if ($artifact === null) { 42 + throw new Exception("Associated Drydock host artifact not found!"); 43 + } 44 + 45 + $data = $artifact->getArtifactData(); 46 + 47 + // FIXME: Is there a better way of doing this? 48 + $lease = id(new DrydockLease())->load( 49 + $data['drydock-lease']); 50 + if ($lease === null) { 51 + throw new Exception("Associated Drydock lease not found!"); 52 + } 53 + $resource = id(new DrydockResource())->load( 54 + $lease->getResourceID()); 55 + if ($resource === null) { 56 + throw new Exception("Associated Drydock resource not found!"); 57 + } 58 + $lease->attachResource($resource); 59 + 60 + $interface = $lease->getInterface('command'); 61 + 62 + $future = $interface->getExecFuture('%C', $command); 63 + 64 + $log_stdout = $build->createLog($build_target, "remote", "stdout"); 65 + $log_stderr = $build->createLog($build_target, "remote", "stderr"); 66 + 67 + $start_stdout = $log_stdout->start(); 68 + $start_stderr = $log_stderr->start(); 69 + 70 + // Read the next amount of available output every second. 71 + while (!$future->isReady()) { 72 + list($stdout, $stderr) = $future->read(); 73 + $log_stdout->append($stdout); 74 + $log_stderr->append($stderr); 75 + $future->discardBuffers(); 76 + 77 + // Check to see if we have moved from a "Building" status. This 78 + // can occur if the user cancels the build, in which case we want 79 + // to terminate whatever we're doing and return as quickly as possible. 80 + if ($build->checkForCancellation()) { 81 + $log_stdout->finalize($start_stdout); 82 + $log_stderr->finalize($start_stderr); 83 + $future->resolveKill(); 84 + return; 85 + } 86 + 87 + // Wait one second before querying for more data. 88 + sleep(1); 89 + } 90 + 91 + // Get the return value so we can log that as well. 92 + list($err) = $future->resolve(); 93 + 94 + // Retrieve the last few bits of information. 95 + list($stdout, $stderr) = $future->read(); 96 + $log_stdout->append($stdout); 97 + $log_stderr->append($stderr); 98 + $future->discardBuffers(); 99 + 100 + $log_stdout->finalize($start_stdout); 101 + $log_stderr->finalize($start_stderr); 102 + 103 + if ($err) { 104 + $build->setBuildStatus(HarbormasterBuild::STATUS_FAILED); 105 + } 106 + } 107 + 108 + public function validateSettings() { 109 + $settings = $this->getSettings(); 110 + 111 + if ($settings['command'] === null || !is_string($settings['command'])) { 112 + return false; 113 + } 114 + if ($settings['hostartifact'] === null || 115 + !is_string($settings['hostartifact'])) { 116 + return false; 117 + } 118 + 119 + // TODO: Check if the host artifact is provided by previous build steps. 120 + 121 + return true; 122 + } 123 + 124 + public function getSettingDefinitions() { 125 + return array( 126 + 'command' => array( 127 + 'name' => 'Command', 128 + 'description' => 'The command to execute on the remote machine.', 129 + 'type' => BuildStepImplementation::SETTING_TYPE_STRING), 130 + 'hostartifact' => array( 131 + 'name' => 'Host Artifact', 132 + 'description' => 133 + 'The host artifact that determines what machine the command '. 134 + 'will run on.', 135 + 'type' => BuildStepImplementation::SETTING_TYPE_ARTIFACT, 136 + 'artifact_type' => HarbormasterBuildArtifact::TYPE_HOST)); 137 + } 138 + 139 + }
+12 -3
src/applications/harbormaster/step/LeaseHostBuildStepImplementation.php
··· 15 15 $settings = $this->getSettings(); 16 16 17 17 return pht( 18 - 'Obtain a lease on a Drydock host whose platform is \'%s\'.', 19 - $settings['platform']); 18 + 'Obtain a lease on a Drydock host whose platform is \'%s\' and store '. 19 + 'the resulting lease in a host artifact called \'%s\'.', 20 + $settings['platform'], 21 + $settings['name']); 20 22 } 21 23 22 24 public function execute( ··· 41 43 $artifact = $build->createArtifact( 42 44 $build_target, 43 45 $settings['name'], 44 - 'host'); 46 + HarbormasterBuildArtifact::TYPE_HOST); 45 47 $artifact->setArtifactData(array( 46 48 'drydock-lease' => $lease->getID())); 47 49 $artifact->save(); 50 + } 51 + 52 + public function getArtifactMappings() { 53 + $settings = $this->getSettings(); 54 + 55 + return array( 56 + $settings['name'] => 'host'); 48 57 } 49 58 50 59 public function validateSettings() {
-151
src/applications/harbormaster/step/RemoteCommandBuildStepImplementation.php
··· 1 - <?php 2 - 3 - final class RemoteCommandBuildStepImplementation 4 - extends VariableBuildStepImplementation { 5 - 6 - public function getName() { 7 - return pht('Run Remote Command'); 8 - } 9 - 10 - public function getGenericDescription() { 11 - return pht('Run a command on another machine.'); 12 - } 13 - 14 - public function getDescription() { 15 - $settings = $this->getSettings(); 16 - 17 - return pht( 18 - 'Run \'%s\' on \'%s\'.', 19 - $settings['command'], 20 - $settings['sshhost']); 21 - } 22 - 23 - public function execute( 24 - HarbormasterBuild $build, 25 - HarbormasterBuildTarget $build_target) { 26 - 27 - $settings = $this->getSettings(); 28 - $variables = $build_target->getVariables(); 29 - 30 - $command = $this->mergeVariables( 31 - 'vcsprintf', 32 - $settings['command'], 33 - $variables); 34 - 35 - $future = null; 36 - if (empty($settings['sshkey'])) { 37 - $future = new ExecFuture( 38 - 'ssh -o "StrictHostKeyChecking no" -p %s %s %s', 39 - $settings['sshport'], 40 - $settings['sshuser'].'@'.$settings['sshhost'], 41 - $command); 42 - } else { 43 - $future = new ExecFuture( 44 - 'ssh -o "StrictHostKeyChecking no" -p %s -i %s %s %s', 45 - $settings['sshport'], 46 - $settings['sshkey'], 47 - $settings['sshuser'].'@'.$settings['sshhost'], 48 - $command); 49 - } 50 - 51 - $log_stdout = $build->createLog($build_target, "remote", "stdout"); 52 - $log_stderr = $build->createLog($build_target, "remote", "stderr"); 53 - 54 - $start_stdout = $log_stdout->start(); 55 - $start_stderr = $log_stderr->start(); 56 - 57 - // Read the next amount of available output every second. 58 - while (!$future->isReady()) { 59 - list($stdout, $stderr) = $future->read(); 60 - $log_stdout->append($stdout); 61 - $log_stderr->append($stderr); 62 - $future->discardBuffers(); 63 - 64 - // Check to see if we have moved from a "Building" status. This 65 - // can occur if the user cancels the build, in which case we want 66 - // to terminate whatever we're doing and return as quickly as possible. 67 - if ($build->checkForCancellation()) { 68 - $log_stdout->finalize($start_stdout); 69 - $log_stderr->finalize($start_stderr); 70 - $future->resolveKill(); 71 - return; 72 - } 73 - 74 - // Wait one second before querying for more data. 75 - sleep(1); 76 - } 77 - 78 - // Get the return value so we can log that as well. 79 - list($err) = $future->resolve(); 80 - 81 - // Retrieve the last few bits of information. 82 - list($stdout, $stderr) = $future->read(); 83 - $log_stdout->append($stdout); 84 - $log_stderr->append($stderr); 85 - $future->discardBuffers(); 86 - 87 - $log_stdout->finalize($start_stdout); 88 - $log_stderr->finalize($start_stderr); 89 - 90 - if ($err) { 91 - $build->setBuildStatus(HarbormasterBuild::STATUS_FAILED); 92 - } 93 - } 94 - 95 - public function validateSettings() { 96 - $settings = $this->getSettings(); 97 - 98 - if ($settings['command'] === null || !is_string($settings['command'])) { 99 - return false; 100 - } 101 - if ($settings['sshhost'] === null || !is_string($settings['sshhost'])) { 102 - return false; 103 - } 104 - if ($settings['sshuser'] === null || !is_string($settings['sshuser'])) { 105 - return false; 106 - } 107 - if ($settings['sshkey'] === null || !is_string($settings['sshkey'])) { 108 - return false; 109 - } 110 - if ($settings['sshport'] === null || !is_int($settings['sshport']) || 111 - $settings['sshport'] <= 0 || $settings['sshport'] >= 65536) { 112 - return false; 113 - } 114 - 115 - $whitelist = PhabricatorEnv::getEnvConfig( 116 - 'harbormaster.temporary.hosts.whitelist'); 117 - if (!in_array($settings['sshhost'], $whitelist)) { 118 - return false; 119 - } 120 - 121 - return true; 122 - } 123 - 124 - public function getSettingDefinitions() { 125 - return array( 126 - 'command' => array( 127 - 'name' => 'Command', 128 - 'description' => 'The command to execute on the remote machine.', 129 - 'type' => BuildStepImplementation::SETTING_TYPE_STRING), 130 - 'sshhost' => array( 131 - 'name' => 'SSH Host', 132 - 'description' => 'The SSH host that the command will be run on.', 133 - 'type' => BuildStepImplementation::SETTING_TYPE_STRING), 134 - 'sshport' => array( 135 - 'name' => 'SSH Port', 136 - 'description' => 'The SSH port to connect to.', 137 - 'type' => BuildStepImplementation::SETTING_TYPE_INTEGER, 138 - 'default' => 22), // TODO: 'default' doesn't do anything yet.. 139 - 'sshuser' => array( 140 - 'name' => 'SSH Username', 141 - 'description' => 'The SSH username to use.', 142 - 'type' => BuildStepImplementation::SETTING_TYPE_STRING), 143 - 'sshkey' => array( 144 - 'name' => 'SSH Identity File', 145 - 'description' => 146 - 'The path to the SSH identity file (private key) '. 147 - 'on the local web server.', 148 - 'type' => BuildStepImplementation::SETTING_TYPE_STRING)); 149 - } 150 - 151 - }
+2 -1
src/applications/harbormaster/step/VariableBuildStepImplementation.php
··· 37 37 } 38 38 39 39 public function getSettingRemarkupInstructions() { 40 + $variables = HarbormasterBuild::getAvailableBuildVariables(); 40 41 $text = ''; 41 42 $text .= pht('The following variables are available: ')."\n"; 42 43 $text .= "\n"; 43 - foreach ($this->getAvailableVariables() as $name => $desc) { 44 + foreach ($variables as $name => $desc) { 44 45 $text .= ' - `'.$name.'`: '.$desc."\n"; 45 46 } 46 47 $text .= "\n";
+2 -2
src/applications/harbormaster/storage/build/HarbormasterBuild.php
··· 113 113 114 114 $artifact = 115 115 HarbormasterBuildArtifact::initializeNewBuildArtifact($build_target); 116 - $artifact->setArtifactKey($artifact_key); 116 + $artifact->setArtifactKey($this->getPHID(), $artifact_key); 117 117 $artifact->setArtifactType($artifact_type); 118 118 $artifact->save(); 119 119 return $artifact; ··· 172 172 return $results; 173 173 } 174 174 175 - public function getAvailableBuildVariables() { 175 + public static function getAvailableBuildVariables() { 176 176 return array( 177 177 'buildable.diff' => 178 178 pht('The differential diff ID, if applicable.'),
+40 -8
src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php
··· 9 9 protected $artifactKey; 10 10 protected $artifactData = array(); 11 11 12 + private $buildTarget = self::ATTACHABLE; 13 + 14 + const TYPE_FILE = 'file'; 15 + const TYPE_HOST = 'host'; 16 + 12 17 public static function initializeNewBuildArtifact( 13 18 HarbormasterBuildTarget $build_target) { 14 19 return id(new HarbormasterBuildArtifact()) ··· 23 28 ) + parent::getConfiguration(); 24 29 } 25 30 26 - public function attachBuildable(HarbormasterBuildable $buildable) { 27 - $this->buildable = $buildable; 31 + public function attachBuildTarget(HarbormasterBuildTarget $build_target) { 32 + $this->buildTarget = $build_target; 28 33 return $this; 29 34 } 30 35 31 - public function getBuildable() { 32 - return $this->assertAttached($this->buildable); 36 + public function getBuildTarget() { 37 + return $this->assertAttached($this->buildTarget); 33 38 } 34 39 35 - public function setArtifactKey($key) { 40 + public function setArtifactKey($build_phid, $key) { 36 41 $this->artifactIndex = 37 - PhabricatorHash::digestForIndex($this->buildTargetPHID.$key); 42 + PhabricatorHash::digestForIndex($build_phid.$key); 38 43 $this->artifactKey = $key; 39 44 return $this; 40 45 } 41 46 47 + public function getObjectItemView(PhabricatorUser $viewer) { 48 + $data = $this->getArtifactData(); 49 + switch ($this->getArtifactType()) { 50 + case self::TYPE_FILE: 51 + $handle = id(new PhabricatorHandleQuery()) 52 + ->setViewer($viewer) 53 + ->withPHIDs($data) 54 + ->executeOne(); 55 + 56 + return id(new PHUIObjectItemView()) 57 + ->setObjectName(pht('File')) 58 + ->setHeader($handle->getFullName()) 59 + ->setHref($handle->getURI()); 60 + case self::TYPE_HOST: 61 + $leases = id(new DrydockLeaseQuery()) 62 + ->withIDs(array($data["drydock-lease"])) 63 + ->execute(); 64 + $lease = $leases[$data["drydock-lease"]]; 65 + 66 + return id(new PHUIObjectItemView()) 67 + ->setObjectName(pht('Drydock Lease')) 68 + ->setHeader($lease->getID()) 69 + ->setHref('/drydock/lease/'.$lease->getID()); 70 + default: 71 + return null; 72 + } 73 + } 42 74 43 75 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 44 76 ··· 50 82 } 51 83 52 84 public function getPolicy($capability) { 53 - return $this->getBuildable()->getPolicy($capability); 85 + return $this->getBuildTarget()->getPolicy($capability); 54 86 } 55 87 56 88 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 57 - return $this->getBuildable()->hasAutomaticCapability( 89 + return $this->getBuildTarget()->hasAutomaticCapability( 58 90 $capability, 59 91 $viewer); 60 92 }