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 @@ -1002,7 +1002,9 @@ 'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php', 'HarbormasterCreateArtifactConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php', 'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php', + 'HarbormasterDrydockCommandBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterDrydockCommandBuildStepImplementation.php', 'HarbormasterDrydockLeaseArtifact' => 'applications/harbormaster/artifact/HarbormasterDrydockLeaseArtifact.php', + 'HarbormasterExecFuture' => 'applications/harbormaster/future/HarbormasterExecFuture.php', 'HarbormasterExternalBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterExternalBuildStepGroup.php', 'HarbormasterFileArtifact' => 'applications/harbormaster/artifact/HarbormasterFileArtifact.php', 'HarbormasterHTTPRequestBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php', @@ -4789,7 +4791,9 @@ 'HarbormasterController' => 'PhabricatorController', 'HarbormasterCreateArtifactConduitAPIMethod' => 'HarbormasterConduitAPIMethod', 'HarbormasterDAO' => 'PhabricatorLiskDAO', + 'HarbormasterDrydockCommandBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterDrydockLeaseArtifact' => 'HarbormasterArtifact', + 'HarbormasterExecFuture' => 'Future', 'HarbormasterExternalBuildStepGroup' => 'HarbormasterBuildStepGroup', 'HarbormasterFileArtifact' => 'HarbormasterArtifact', 'HarbormasterHTTPRequestBuildStepImplementation' => 'HarbormasterBuildStepImplementation', diff --git a/src/applications/harbormaster/future/HarbormasterExecFuture.php b/src/applications/harbormaster/future/HarbormasterExecFuture.php new file mode 100644 --- /dev/null +++ b/src/applications/harbormaster/future/HarbormasterExecFuture.php @@ -0,0 +1,50 @@ +future = $future; + return $this; + } + + public function getFuture() { + return $this->future; + } + + public function setLogs( + HarbormasterBuildLog $stdout, + HarbormasterBuildLog $stderr) { + $this->stdout = $stdout; + $this->stderr = $stderr; + return $this; + } + + public function isReady() { + $future = $this->getFuture(); + + $result = $future->isReady(); + + list($stdout, $stderr) = $future->read(); + $future->discardBuffers(); + + if ($this->stdout) { + $this->stdout->append($stdout); + } + + if ($this->stderr) { + $this->stderr->append($stderr); + } + + return $result; + } + + protected function getResult() { + return $this->getFuture()->getResult(); + } + +} diff --git a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php --- a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php @@ -238,22 +238,21 @@ return $build->getBuildGeneration() !== $target->getBuildGeneration(); } - protected function resolveFuture( + protected function resolveFutures( HarbormasterBuild $build, HarbormasterBuildTarget $target, - Future $future) { + array $futures) { - $futures = new FutureIterator(array($future)); + $futures = new FutureIterator($futures); foreach ($futures->setUpdateInterval(5) as $key => $future) { if ($future === null) { $build->reload(); if ($this->shouldAbort($build, $target)) { throw new HarbormasterBuildAbortedException(); } - } else { - return $future->resolve(); } } + } diff --git a/src/applications/harbormaster/step/HarbormasterDrydockCommandBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterDrydockCommandBuildStepImplementation.php new file mode 100644 --- /dev/null +++ b/src/applications/harbormaster/step/HarbormasterDrydockCommandBuildStepImplementation.php @@ -0,0 +1,90 @@ +formatSettingForDescription('command'), + $this->formatSettingForDescription('artifact')); + } + + public function execute( + HarbormasterBuild $build, + HarbormasterBuildTarget $build_target) { + $viewer = PhabricatorUser::getOmnipotentUser(); + + $settings = $this->getSettings(); + $variables = $build_target->getVariables(); + + $artifact = $build_target->loadArtifact($settings['artifact']); + $impl = $artifact->getArtifactImplementation(); + $lease = $impl->loadArtifactLease($viewer); + + // TODO: Require active lease. + + $command = $this->mergeVariables( + 'vcsprintf', + $settings['command'], + $variables); + + $interface = $lease->getInterface(DrydockCommandInterface::INTERFACE_TYPE); + + $exec_future = $interface->getExecFuture('%C', $command); + + $harbor_future = id(new HarbormasterExecFuture()) + ->setFuture($exec_future) + ->setLogs( + $build_target->newLog('remote', 'stdout'), + $build_target->newLog('remote', 'stderr')); + + $this->resolveFutures( + $build, + $build_target, + array($harbor_future)); + + list($err) = $harbor_future->resolve(); + if ($err) { + throw new HarbormasterBuildFailureException(); + } + } + + public function getArtifactInputs() { + return array( + array( + 'name' => pht('Drydock Lease'), + 'key' => $this->getSetting('artifact'), + 'type' => HarbormasterWorkingCopyArtifact::ARTIFACTCONST, + ), + ); + } + + public function getFieldSpecifications() { + return array( + 'command' => array( + 'name' => pht('Command'), + 'type' => 'text', + 'required' => true, + ), + 'artifact' => array( + 'name' => pht('Drydock Lease'), + 'type' => 'text', + 'required' => true, + ), + ); + } + +} diff --git a/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php --- a/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php @@ -51,9 +51,6 @@ $settings['uri'], $variables); - $log_body = $build->createLog($build_target, $uri, 'http-body'); - $start = $log_body->start(); - $method = nonempty(idx($settings, 'method'), 'POST'); $future = id(new HTTPSFuture($uri)) @@ -70,16 +67,30 @@ $key->getPasswordEnvelope()); } - list($status, $body, $headers) = $this->resolveFuture( + $this->resolveFutures( $build, $build_target, - $future); + array($future)); + + list($status, $body, $headers) = $future->resolve(); + + $header_lines = array(); + foreach ($headers as $header) { + list($head, $tail) = $header; + $header_lines[] = "{$head}: {$tail}"; + } + $header_lines = implode("\n", $header_lines); + + $build_target + ->newLog($uri, 'http.head') + ->append($header_lines); - $log_body->append($body); - $log_body->finalize($start); + $build_target + ->newLog($uri, 'http.body') + ->append($body); if ($status->getStatusCode() != 200) { - $build->setBuildStatus(HarbormasterBuild::STATUS_FAILED); + throw new HarbormasterBuildFailureException(); } } 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 @@ -88,7 +88,7 @@ array( 'name' => pht('Working Copy'), 'key' => $this->getSetting('name'), - 'type' => HarbormasterHostArtifact::ARTIFACTCONST, + 'type' => HarbormasterWorkingCopyArtifact::ARTIFACTCONST, ), ); } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php --- a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php @@ -10,6 +10,7 @@ protected $live; private $buildTarget = self::ATTACHABLE; + private $start; const CHUNK_BYTE_LIMIT = 102400; @@ -18,6 +19,12 @@ */ const ENCODING_TEXT = 'text'; + public function __destruct() { + if ($this->live) { + $this->finalize($this->start); + } + } + public static function initializeNewBuildLog( HarbormasterBuildTarget $build_target) { @@ -75,6 +82,8 @@ $this->setLive(1); $this->save(); + $this->start = PhabricatorTime::getNow(); + return time(); } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php --- a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php @@ -249,6 +249,20 @@ return $artifact; } + public function newLog($log_source, $log_type) { + $log_source = id(new PhutilUTF8StringTruncator()) + ->setMaximumBytes(250) + ->truncateString($log_source); + + $log = HarbormasterBuildLog::initializeNewBuildLog($this) + ->setLogSource($log_source) + ->setLogType($log_type); + + $log->start(); + + return $log; + } + /* -( Status )------------------------------------------------------------- */