1
0
Fork 0
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:
epriestley 2015-09-25 10:43:32 -07:00
parent bd546f44a9
commit d735c7adf2
8 changed files with 191 additions and 14 deletions

View file

@ -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',

View file

@ -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();
}
}

View file

@ -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();
}
}
}

View file

@ -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,
),
);
}
}

View file

@ -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();
}
}

View file

@ -88,7 +88,7 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation
array(
'name' => pht('Working Copy'),
'key' => $this->getSetting('name'),
'type' => HarbormasterHostArtifact::ARTIFACTCONST,
'type' => HarbormasterWorkingCopyArtifact::ARTIFACTCONST,
),
);
}

View file

@ -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();
}

View file

@ -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 )------------------------------------------------------------- */