mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-13 02:12: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',
|
'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php',
|
||||||
'HarbormasterCreateArtifactConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php',
|
'HarbormasterCreateArtifactConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php',
|
||||||
'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php',
|
'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php',
|
||||||
|
'HarbormasterDrydockCommandBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterDrydockCommandBuildStepImplementation.php',
|
||||||
'HarbormasterDrydockLeaseArtifact' => 'applications/harbormaster/artifact/HarbormasterDrydockLeaseArtifact.php',
|
'HarbormasterDrydockLeaseArtifact' => 'applications/harbormaster/artifact/HarbormasterDrydockLeaseArtifact.php',
|
||||||
|
'HarbormasterExecFuture' => 'applications/harbormaster/future/HarbormasterExecFuture.php',
|
||||||
'HarbormasterExternalBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterExternalBuildStepGroup.php',
|
'HarbormasterExternalBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterExternalBuildStepGroup.php',
|
||||||
'HarbormasterFileArtifact' => 'applications/harbormaster/artifact/HarbormasterFileArtifact.php',
|
'HarbormasterFileArtifact' => 'applications/harbormaster/artifact/HarbormasterFileArtifact.php',
|
||||||
'HarbormasterHTTPRequestBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php',
|
'HarbormasterHTTPRequestBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php',
|
||||||
|
@ -4789,7 +4791,9 @@ phutil_register_library_map(array(
|
||||||
'HarbormasterController' => 'PhabricatorController',
|
'HarbormasterController' => 'PhabricatorController',
|
||||||
'HarbormasterCreateArtifactConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
|
'HarbormasterCreateArtifactConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
|
||||||
'HarbormasterDAO' => 'PhabricatorLiskDAO',
|
'HarbormasterDAO' => 'PhabricatorLiskDAO',
|
||||||
|
'HarbormasterDrydockCommandBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
|
||||||
'HarbormasterDrydockLeaseArtifact' => 'HarbormasterArtifact',
|
'HarbormasterDrydockLeaseArtifact' => 'HarbormasterArtifact',
|
||||||
|
'HarbormasterExecFuture' => 'Future',
|
||||||
'HarbormasterExternalBuildStepGroup' => 'HarbormasterBuildStepGroup',
|
'HarbormasterExternalBuildStepGroup' => 'HarbormasterBuildStepGroup',
|
||||||
'HarbormasterFileArtifact' => 'HarbormasterArtifact',
|
'HarbormasterFileArtifact' => 'HarbormasterArtifact',
|
||||||
'HarbormasterHTTPRequestBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
|
'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();
|
return $build->getBuildGeneration() !== $target->getBuildGeneration();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function resolveFuture(
|
protected function resolveFutures(
|
||||||
HarbormasterBuild $build,
|
HarbormasterBuild $build,
|
||||||
HarbormasterBuildTarget $target,
|
HarbormasterBuildTarget $target,
|
||||||
Future $future) {
|
array $futures) {
|
||||||
|
|
||||||
$futures = new FutureIterator(array($future));
|
$futures = new FutureIterator($futures);
|
||||||
foreach ($futures->setUpdateInterval(5) as $key => $future) {
|
foreach ($futures->setUpdateInterval(5) as $key => $future) {
|
||||||
if ($future === null) {
|
if ($future === null) {
|
||||||
$build->reload();
|
$build->reload();
|
||||||
if ($this->shouldAbort($build, $target)) {
|
if ($this->shouldAbort($build, $target)) {
|
||||||
throw new HarbormasterBuildAbortedException();
|
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'],
|
$settings['uri'],
|
||||||
$variables);
|
$variables);
|
||||||
|
|
||||||
$log_body = $build->createLog($build_target, $uri, 'http-body');
|
|
||||||
$start = $log_body->start();
|
|
||||||
|
|
||||||
$method = nonempty(idx($settings, 'method'), 'POST');
|
$method = nonempty(idx($settings, 'method'), 'POST');
|
||||||
|
|
||||||
$future = id(new HTTPSFuture($uri))
|
$future = id(new HTTPSFuture($uri))
|
||||||
|
@ -70,16 +67,30 @@ final class HarbormasterHTTPRequestBuildStepImplementation
|
||||||
$key->getPasswordEnvelope());
|
$key->getPasswordEnvelope());
|
||||||
}
|
}
|
||||||
|
|
||||||
list($status, $body, $headers) = $this->resolveFuture(
|
$this->resolveFutures(
|
||||||
$build,
|
$build,
|
||||||
$build_target,
|
$build_target,
|
||||||
$future);
|
array($future));
|
||||||
|
|
||||||
$log_body->append($body);
|
list($status, $body, $headers) = $future->resolve();
|
||||||
$log_body->finalize($start);
|
|
||||||
|
$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) {
|
if ($status->getStatusCode() != 200) {
|
||||||
$build->setBuildStatus(HarbormasterBuild::STATUS_FAILED);
|
throw new HarbormasterBuildFailureException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,7 +88,7 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation
|
||||||
array(
|
array(
|
||||||
'name' => pht('Working Copy'),
|
'name' => pht('Working Copy'),
|
||||||
'key' => $this->getSetting('name'),
|
'key' => $this->getSetting('name'),
|
||||||
'type' => HarbormasterHostArtifact::ARTIFACTCONST,
|
'type' => HarbormasterWorkingCopyArtifact::ARTIFACTCONST,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ final class HarbormasterBuildLog extends HarbormasterDAO
|
||||||
protected $live;
|
protected $live;
|
||||||
|
|
||||||
private $buildTarget = self::ATTACHABLE;
|
private $buildTarget = self::ATTACHABLE;
|
||||||
|
private $start;
|
||||||
|
|
||||||
const CHUNK_BYTE_LIMIT = 102400;
|
const CHUNK_BYTE_LIMIT = 102400;
|
||||||
|
|
||||||
|
@ -18,6 +19,12 @@ final class HarbormasterBuildLog extends HarbormasterDAO
|
||||||
*/
|
*/
|
||||||
const ENCODING_TEXT = 'text';
|
const ENCODING_TEXT = 'text';
|
||||||
|
|
||||||
|
public function __destruct() {
|
||||||
|
if ($this->live) {
|
||||||
|
$this->finalize($this->start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static function initializeNewBuildLog(
|
public static function initializeNewBuildLog(
|
||||||
HarbormasterBuildTarget $build_target) {
|
HarbormasterBuildTarget $build_target) {
|
||||||
|
|
||||||
|
@ -75,6 +82,8 @@ final class HarbormasterBuildLog extends HarbormasterDAO
|
||||||
$this->setLive(1);
|
$this->setLive(1);
|
||||||
$this->save();
|
$this->save();
|
||||||
|
|
||||||
|
$this->start = PhabricatorTime::getNow();
|
||||||
|
|
||||||
return time();
|
return time();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -249,6 +249,20 @@ final class HarbormasterBuildTarget extends HarbormasterDAO
|
||||||
return $artifact;
|
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 )------------------------------------------------------------- */
|
/* -( Status )------------------------------------------------------------- */
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue