mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-10 00:42:41 +01:00
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
This commit is contained in:
parent
bd546f44a9
commit
d735c7adf2
8 changed files with 191 additions and 14 deletions
|
@ -1002,7 +1002,9 @@ phutil_register_library_map(array(
|
|||
'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 @@ phutil_register_library_map(array(
|
|||
'HarbormasterController' => 'PhabricatorController',
|
||||
'HarbormasterCreateArtifactConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
|
||||
'HarbormasterDAO' => 'PhabricatorLiskDAO',
|
||||
'HarbormasterDrydockCommandBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
|
||||
'HarbormasterDrydockLeaseArtifact' => 'HarbormasterArtifact',
|
||||
'HarbormasterExecFuture' => 'Future',
|
||||
'HarbormasterExternalBuildStepGroup' => 'HarbormasterBuildStepGroup',
|
||||
'HarbormasterFileArtifact' => 'HarbormasterArtifact',
|
||||
'HarbormasterHTTPRequestBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterExecFuture
|
||||
extends Future {
|
||||
|
||||
private $future;
|
||||
private $stdout;
|
||||
private $stderr;
|
||||
|
||||
public function setFuture(ExecFuture $future) {
|
||||
$this->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();
|
||||
}
|
||||
|
||||
}
|
|
@ -238,22 +238,21 @@ abstract class HarbormasterBuildStepImplementation extends Phobject {
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterDrydockCommandBuildStepImplementation
|
||||
extends HarbormasterBuildStepImplementation {
|
||||
|
||||
public function getName() {
|
||||
return pht('Drydock: Run Command');
|
||||
}
|
||||
|
||||
public function getGenericDescription() {
|
||||
return pht('Run a command on Drydock resource.');
|
||||
}
|
||||
|
||||
public function getBuildStepGroupKey() {
|
||||
return HarbormasterPrototypeBuildStepGroup::GROUPKEY;
|
||||
}
|
||||
|
||||
public function getDescription() {
|
||||
return pht(
|
||||
'Run command %s on %s.',
|
||||
$this->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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -51,9 +51,6 @@ final class HarbormasterHTTPRequestBuildStepImplementation
|
|||
$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 @@ final class HarbormasterHTTPRequestBuildStepImplementation
|
|||
$key->getPasswordEnvelope());
|
||||
}
|
||||
|
||||
list($status, $body, $headers) = $this->resolveFuture(
|
||||
$this->resolveFutures(
|
||||
$build,
|
||||
$build_target,
|
||||
$future);
|
||||
array($future));
|
||||
|
||||
$log_body->append($body);
|
||||
$log_body->finalize($start);
|
||||
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);
|
||||
|
||||
$build_target
|
||||
->newLog($uri, 'http.body')
|
||||
->append($body);
|
||||
|
||||
if ($status->getStatusCode() != 200) {
|
||||
$build->setBuildStatus(HarbormasterBuild::STATUS_FAILED);
|
||||
throw new HarbormasterBuildFailureException();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation
|
|||
array(
|
||||
'name' => pht('Working Copy'),
|
||||
'key' => $this->getSetting('name'),
|
||||
'type' => HarbormasterHostArtifact::ARTIFACTCONST,
|
||||
'type' => HarbormasterWorkingCopyArtifact::ARTIFACTCONST,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ final class HarbormasterBuildLog extends HarbormasterDAO
|
|||
protected $live;
|
||||
|
||||
private $buildTarget = self::ATTACHABLE;
|
||||
private $start;
|
||||
|
||||
const CHUNK_BYTE_LIMIT = 102400;
|
||||
|
||||
|
@ -18,6 +19,12 @@ final class HarbormasterBuildLog extends HarbormasterDAO
|
|||
*/
|
||||
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 @@ final class HarbormasterBuildLog extends HarbormasterDAO
|
|||
$this->setLive(1);
|
||||
$this->save();
|
||||
|
||||
$this->start = PhabricatorTime::getNow();
|
||||
|
||||
return time();
|
||||
}
|
||||
|
||||
|
|
|
@ -249,6 +249,20 @@ final class HarbormasterBuildTarget extends HarbormasterDAO
|
|||
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 )------------------------------------------------------------- */
|
||||
|
||||
|
|
Loading…
Reference in a new issue