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

Allow Harbormaster to run commands on Drydock working copies

Summary: Ref T9252. This mostly cleans up future and log handling, and edges us closer to being able to do useful work with Harbormaster / Drydock.

Test Plan:
- Added a "Run `ls -alh`" step to my trivial build plan.
- Ran it a bunch of times.
- Worked great.
- Also did an HTTP plan.

{F835227}

{F835228}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T9252

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

+191 -14
+4
src/__phutil_library_map__.php
··· 1002 1002 'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php', 1003 1003 'HarbormasterCreateArtifactConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php', 1004 1004 'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php', 1005 + 'HarbormasterDrydockCommandBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterDrydockCommandBuildStepImplementation.php', 1005 1006 'HarbormasterDrydockLeaseArtifact' => 'applications/harbormaster/artifact/HarbormasterDrydockLeaseArtifact.php', 1007 + 'HarbormasterExecFuture' => 'applications/harbormaster/future/HarbormasterExecFuture.php', 1006 1008 'HarbormasterExternalBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterExternalBuildStepGroup.php', 1007 1009 'HarbormasterFileArtifact' => 'applications/harbormaster/artifact/HarbormasterFileArtifact.php', 1008 1010 'HarbormasterHTTPRequestBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php', ··· 4789 4791 'HarbormasterController' => 'PhabricatorController', 4790 4792 'HarbormasterCreateArtifactConduitAPIMethod' => 'HarbormasterConduitAPIMethod', 4791 4793 'HarbormasterDAO' => 'PhabricatorLiskDAO', 4794 + 'HarbormasterDrydockCommandBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 4792 4795 'HarbormasterDrydockLeaseArtifact' => 'HarbormasterArtifact', 4796 + 'HarbormasterExecFuture' => 'Future', 4793 4797 'HarbormasterExternalBuildStepGroup' => 'HarbormasterBuildStepGroup', 4794 4798 'HarbormasterFileArtifact' => 'HarbormasterArtifact', 4795 4799 'HarbormasterHTTPRequestBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
+50
src/applications/harbormaster/future/HarbormasterExecFuture.php
··· 1 + <?php 2 + 3 + final class HarbormasterExecFuture 4 + extends Future { 5 + 6 + private $future; 7 + private $stdout; 8 + private $stderr; 9 + 10 + public function setFuture(ExecFuture $future) { 11 + $this->future = $future; 12 + return $this; 13 + } 14 + 15 + public function getFuture() { 16 + return $this->future; 17 + } 18 + 19 + public function setLogs( 20 + HarbormasterBuildLog $stdout, 21 + HarbormasterBuildLog $stderr) { 22 + $this->stdout = $stdout; 23 + $this->stderr = $stderr; 24 + return $this; 25 + } 26 + 27 + public function isReady() { 28 + $future = $this->getFuture(); 29 + 30 + $result = $future->isReady(); 31 + 32 + list($stdout, $stderr) = $future->read(); 33 + $future->discardBuffers(); 34 + 35 + if ($this->stdout) { 36 + $this->stdout->append($stdout); 37 + } 38 + 39 + if ($this->stderr) { 40 + $this->stderr->append($stderr); 41 + } 42 + 43 + return $result; 44 + } 45 + 46 + protected function getResult() { 47 + return $this->getFuture()->getResult(); 48 + } 49 + 50 + }
+4 -5
src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php
··· 238 238 return $build->getBuildGeneration() !== $target->getBuildGeneration(); 239 239 } 240 240 241 - protected function resolveFuture( 241 + protected function resolveFutures( 242 242 HarbormasterBuild $build, 243 243 HarbormasterBuildTarget $target, 244 - Future $future) { 244 + array $futures) { 245 245 246 - $futures = new FutureIterator(array($future)); 246 + $futures = new FutureIterator($futures); 247 247 foreach ($futures->setUpdateInterval(5) as $key => $future) { 248 248 if ($future === null) { 249 249 $build->reload(); 250 250 if ($this->shouldAbort($build, $target)) { 251 251 throw new HarbormasterBuildAbortedException(); 252 252 } 253 - } else { 254 - return $future->resolve(); 255 253 } 256 254 } 255 + 257 256 } 258 257 259 258
+90
src/applications/harbormaster/step/HarbormasterDrydockCommandBuildStepImplementation.php
··· 1 + <?php 2 + 3 + final class HarbormasterDrydockCommandBuildStepImplementation 4 + extends HarbormasterBuildStepImplementation { 5 + 6 + public function getName() { 7 + return pht('Drydock: Run Command'); 8 + } 9 + 10 + public function getGenericDescription() { 11 + return pht('Run a command on Drydock resource.'); 12 + } 13 + 14 + public function getBuildStepGroupKey() { 15 + return HarbormasterPrototypeBuildStepGroup::GROUPKEY; 16 + } 17 + 18 + public function getDescription() { 19 + return pht( 20 + 'Run command %s on %s.', 21 + $this->formatSettingForDescription('command'), 22 + $this->formatSettingForDescription('artifact')); 23 + } 24 + 25 + public function execute( 26 + HarbormasterBuild $build, 27 + HarbormasterBuildTarget $build_target) { 28 + $viewer = PhabricatorUser::getOmnipotentUser(); 29 + 30 + $settings = $this->getSettings(); 31 + $variables = $build_target->getVariables(); 32 + 33 + $artifact = $build_target->loadArtifact($settings['artifact']); 34 + $impl = $artifact->getArtifactImplementation(); 35 + $lease = $impl->loadArtifactLease($viewer); 36 + 37 + // TODO: Require active lease. 38 + 39 + $command = $this->mergeVariables( 40 + 'vcsprintf', 41 + $settings['command'], 42 + $variables); 43 + 44 + $interface = $lease->getInterface(DrydockCommandInterface::INTERFACE_TYPE); 45 + 46 + $exec_future = $interface->getExecFuture('%C', $command); 47 + 48 + $harbor_future = id(new HarbormasterExecFuture()) 49 + ->setFuture($exec_future) 50 + ->setLogs( 51 + $build_target->newLog('remote', 'stdout'), 52 + $build_target->newLog('remote', 'stderr')); 53 + 54 + $this->resolveFutures( 55 + $build, 56 + $build_target, 57 + array($harbor_future)); 58 + 59 + list($err) = $harbor_future->resolve(); 60 + if ($err) { 61 + throw new HarbormasterBuildFailureException(); 62 + } 63 + } 64 + 65 + public function getArtifactInputs() { 66 + return array( 67 + array( 68 + 'name' => pht('Drydock Lease'), 69 + 'key' => $this->getSetting('artifact'), 70 + 'type' => HarbormasterWorkingCopyArtifact::ARTIFACTCONST, 71 + ), 72 + ); 73 + } 74 + 75 + public function getFieldSpecifications() { 76 + return array( 77 + 'command' => array( 78 + 'name' => pht('Command'), 79 + 'type' => 'text', 80 + 'required' => true, 81 + ), 82 + 'artifact' => array( 83 + 'name' => pht('Drydock Lease'), 84 + 'type' => 'text', 85 + 'required' => true, 86 + ), 87 + ); 88 + } 89 + 90 + }
+19 -8
src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php
··· 51 51 $settings['uri'], 52 52 $variables); 53 53 54 - $log_body = $build->createLog($build_target, $uri, 'http-body'); 55 - $start = $log_body->start(); 56 - 57 54 $method = nonempty(idx($settings, 'method'), 'POST'); 58 55 59 56 $future = id(new HTTPSFuture($uri)) ··· 70 67 $key->getPasswordEnvelope()); 71 68 } 72 69 73 - list($status, $body, $headers) = $this->resolveFuture( 70 + $this->resolveFutures( 74 71 $build, 75 72 $build_target, 76 - $future); 73 + array($future)); 74 + 75 + list($status, $body, $headers) = $future->resolve(); 76 + 77 + $header_lines = array(); 78 + foreach ($headers as $header) { 79 + list($head, $tail) = $header; 80 + $header_lines[] = "{$head}: {$tail}"; 81 + } 82 + $header_lines = implode("\n", $header_lines); 83 + 84 + $build_target 85 + ->newLog($uri, 'http.head') 86 + ->append($header_lines); 77 87 78 - $log_body->append($body); 79 - $log_body->finalize($start); 88 + $build_target 89 + ->newLog($uri, 'http.body') 90 + ->append($body); 80 91 81 92 if ($status->getStatusCode() != 200) { 82 - $build->setBuildStatus(HarbormasterBuild::STATUS_FAILED); 93 + throw new HarbormasterBuildFailureException(); 83 94 } 84 95 } 85 96
+1 -1
src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php
··· 88 88 array( 89 89 'name' => pht('Working Copy'), 90 90 'key' => $this->getSetting('name'), 91 - 'type' => HarbormasterHostArtifact::ARTIFACTCONST, 91 + 'type' => HarbormasterWorkingCopyArtifact::ARTIFACTCONST, 92 92 ), 93 93 ); 94 94 }
+9
src/applications/harbormaster/storage/build/HarbormasterBuildLog.php
··· 10 10 protected $live; 11 11 12 12 private $buildTarget = self::ATTACHABLE; 13 + private $start; 13 14 14 15 const CHUNK_BYTE_LIMIT = 102400; 15 16 ··· 17 18 * The log is encoded as plain text. 18 19 */ 19 20 const ENCODING_TEXT = 'text'; 21 + 22 + public function __destruct() { 23 + if ($this->live) { 24 + $this->finalize($this->start); 25 + } 26 + } 20 27 21 28 public static function initializeNewBuildLog( 22 29 HarbormasterBuildTarget $build_target) { ··· 74 81 75 82 $this->setLive(1); 76 83 $this->save(); 84 + 85 + $this->start = PhabricatorTime::getNow(); 77 86 78 87 return time(); 79 88 }
+14
src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php
··· 249 249 return $artifact; 250 250 } 251 251 252 + public function newLog($log_source, $log_type) { 253 + $log_source = id(new PhutilUTF8StringTruncator()) 254 + ->setMaximumBytes(250) 255 + ->truncateString($log_source); 256 + 257 + $log = HarbormasterBuildLog::initializeNewBuildLog($this) 258 + ->setLogSource($log_source) 259 + ->setLogType($log_type); 260 + 261 + $log->start(); 262 + 263 + return $log; 264 + } 265 + 252 266 253 267 /* -( Status )------------------------------------------------------------- */ 254 268