mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-01 18:30:59 +01:00
Implement "Upload Artifact" build step
Summary: This implements a build step for uploading an artifact from a build machine to Phabricator. It uses SFTP so that it will work on both UNIX and Windows build machines. Test Plan: Ran an "Upload Artifact" build against a Windows machine (with FreeSSHD installed). The artifact uploaded to Phabricator, appeared on the build view and the file contents could be viewed from Phabricator. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley CC: Korvin, epriestley, aran Maniphest Tasks: T1049 Differential Revision: https://secure.phabricator.com/D7582
This commit is contained in:
parent
a74bfe5167
commit
dd01535ed6
9 changed files with 236 additions and 24 deletions
|
@ -642,6 +642,7 @@ phutil_register_library_map(array(
|
||||||
'DrydockConstants' => 'applications/drydock/constants/DrydockConstants.php',
|
'DrydockConstants' => 'applications/drydock/constants/DrydockConstants.php',
|
||||||
'DrydockController' => 'applications/drydock/controller/DrydockController.php',
|
'DrydockController' => 'applications/drydock/controller/DrydockController.php',
|
||||||
'DrydockDAO' => 'applications/drydock/storage/DrydockDAO.php',
|
'DrydockDAO' => 'applications/drydock/storage/DrydockDAO.php',
|
||||||
|
'DrydockFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockFilesystemInterface.php',
|
||||||
'DrydockInterface' => 'applications/drydock/interface/DrydockInterface.php',
|
'DrydockInterface' => 'applications/drydock/interface/DrydockInterface.php',
|
||||||
'DrydockLease' => 'applications/drydock/storage/DrydockLease.php',
|
'DrydockLease' => 'applications/drydock/storage/DrydockLease.php',
|
||||||
'DrydockLeaseListController' => 'applications/drydock/controller/DrydockLeaseListController.php',
|
'DrydockLeaseListController' => 'applications/drydock/controller/DrydockLeaseListController.php',
|
||||||
|
@ -668,6 +669,7 @@ phutil_register_library_map(array(
|
||||||
'DrydockResourceQuery' => 'applications/drydock/query/DrydockResourceQuery.php',
|
'DrydockResourceQuery' => 'applications/drydock/query/DrydockResourceQuery.php',
|
||||||
'DrydockResourceStatus' => 'applications/drydock/constants/DrydockResourceStatus.php',
|
'DrydockResourceStatus' => 'applications/drydock/constants/DrydockResourceStatus.php',
|
||||||
'DrydockResourceViewController' => 'applications/drydock/controller/DrydockResourceViewController.php',
|
'DrydockResourceViewController' => 'applications/drydock/controller/DrydockResourceViewController.php',
|
||||||
|
'DrydockSFTPFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockSFTPFilesystemInterface.php',
|
||||||
'DrydockSSHCommandInterface' => 'applications/drydock/interface/command/DrydockSSHCommandInterface.php',
|
'DrydockSSHCommandInterface' => 'applications/drydock/interface/command/DrydockSSHCommandInterface.php',
|
||||||
'DrydockWebrootInterface' => 'applications/drydock/interface/webroot/DrydockWebrootInterface.php',
|
'DrydockWebrootInterface' => 'applications/drydock/interface/webroot/DrydockWebrootInterface.php',
|
||||||
'DrydockWorkingCopyBlueprintImplementation' => 'applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php',
|
'DrydockWorkingCopyBlueprintImplementation' => 'applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php',
|
||||||
|
@ -2332,6 +2334,7 @@ phutil_register_library_map(array(
|
||||||
'SleepBuildStepImplementation' => 'applications/harbormaster/step/SleepBuildStepImplementation.php',
|
'SleepBuildStepImplementation' => 'applications/harbormaster/step/SleepBuildStepImplementation.php',
|
||||||
'SlowvoteEmbedView' => 'applications/slowvote/view/SlowvoteEmbedView.php',
|
'SlowvoteEmbedView' => 'applications/slowvote/view/SlowvoteEmbedView.php',
|
||||||
'SlowvoteRemarkupRule' => 'applications/slowvote/remarkup/SlowvoteRemarkupRule.php',
|
'SlowvoteRemarkupRule' => 'applications/slowvote/remarkup/SlowvoteRemarkupRule.php',
|
||||||
|
'UploadArtifactBuildStepImplementation' => 'applications/harbormaster/step/UploadArtifactBuildStepImplementation.php',
|
||||||
'VariableBuildStepImplementation' => 'applications/harbormaster/step/VariableBuildStepImplementation.php',
|
'VariableBuildStepImplementation' => 'applications/harbormaster/step/VariableBuildStepImplementation.php',
|
||||||
),
|
),
|
||||||
'function' =>
|
'function' =>
|
||||||
|
@ -2987,6 +2990,7 @@ phutil_register_library_map(array(
|
||||||
'DrydockCommandInterface' => 'DrydockInterface',
|
'DrydockCommandInterface' => 'DrydockInterface',
|
||||||
'DrydockController' => 'PhabricatorController',
|
'DrydockController' => 'PhabricatorController',
|
||||||
'DrydockDAO' => 'PhabricatorLiskDAO',
|
'DrydockDAO' => 'PhabricatorLiskDAO',
|
||||||
|
'DrydockFilesystemInterface' => 'DrydockInterface',
|
||||||
'DrydockLease' => 'DrydockDAO',
|
'DrydockLease' => 'DrydockDAO',
|
||||||
'DrydockLeaseListController' => 'DrydockController',
|
'DrydockLeaseListController' => 'DrydockController',
|
||||||
'DrydockLeaseQuery' => 'PhabricatorOffsetPagedQuery',
|
'DrydockLeaseQuery' => 'PhabricatorOffsetPagedQuery',
|
||||||
|
@ -3016,6 +3020,7 @@ phutil_register_library_map(array(
|
||||||
'DrydockResourceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
'DrydockResourceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
'DrydockResourceStatus' => 'DrydockConstants',
|
'DrydockResourceStatus' => 'DrydockConstants',
|
||||||
'DrydockResourceViewController' => 'DrydockController',
|
'DrydockResourceViewController' => 'DrydockController',
|
||||||
|
'DrydockSFTPFilesystemInterface' => 'DrydockFilesystemInterface',
|
||||||
'DrydockSSHCommandInterface' => 'DrydockCommandInterface',
|
'DrydockSSHCommandInterface' => 'DrydockCommandInterface',
|
||||||
'DrydockWebrootInterface' => 'DrydockInterface',
|
'DrydockWebrootInterface' => 'DrydockInterface',
|
||||||
'DrydockWorkingCopyBlueprintImplementation' => 'DrydockBlueprintImplementation',
|
'DrydockWorkingCopyBlueprintImplementation' => 'DrydockBlueprintImplementation',
|
||||||
|
@ -4973,6 +4978,7 @@ phutil_register_library_map(array(
|
||||||
'SleepBuildStepImplementation' => 'BuildStepImplementation',
|
'SleepBuildStepImplementation' => 'BuildStepImplementation',
|
||||||
'SlowvoteEmbedView' => 'AphrontView',
|
'SlowvoteEmbedView' => 'AphrontView',
|
||||||
'SlowvoteRemarkupRule' => 'PhabricatorRemarkupRuleObject',
|
'SlowvoteRemarkupRule' => 'PhabricatorRemarkupRuleObject',
|
||||||
|
'UploadArtifactBuildStepImplementation' => 'VariableBuildStepImplementation',
|
||||||
'VariableBuildStepImplementation' => 'BuildStepImplementation',
|
'VariableBuildStepImplementation' => 'BuildStepImplementation',
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
|
@ -105,6 +105,12 @@ final class DrydockPreallocatedHostBlueprintImplementation
|
||||||
'port' => $resource->getAttribute('port'),
|
'port' => $resource->getAttribute('port'),
|
||||||
'credential' => $resource->getAttribute('credential'),
|
'credential' => $resource->getAttribute('credential'),
|
||||||
'platform' => $resource->getAttribute('platform')));
|
'platform' => $resource->getAttribute('platform')));
|
||||||
|
case 'filesystem':
|
||||||
|
return id(new DrydockSFTPFilesystemInterface())
|
||||||
|
->setConfiguration(array(
|
||||||
|
'host' => $resource->getAttribute('host'),
|
||||||
|
'port' => $resource->getAttribute('port'),
|
||||||
|
'credential' => $resource->getAttribute('credential')));
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Exception("No interface of type '{$type}'.");
|
throw new Exception("No interface of type '{$type}'.");
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
abstract class DrydockFilesystemInterface extends DrydockInterface {
|
||||||
|
|
||||||
|
final public function getInterfaceType() {
|
||||||
|
return 'filesystem';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a file on the Drydock resource and returns the contents of the file.
|
||||||
|
*/
|
||||||
|
abstract public function readFile($path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a file on the Drydock resource and saves it as a PhabricatorFile.
|
||||||
|
*/
|
||||||
|
abstract public function saveFile($path, $name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a file to the Drydock resource.
|
||||||
|
*/
|
||||||
|
abstract public function writeFile($path, $data);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class DrydockSFTPFilesystemInterface extends DrydockFilesystemInterface {
|
||||||
|
|
||||||
|
private $passphraseSSHKey;
|
||||||
|
|
||||||
|
private function openCredentialsIfNotOpen() {
|
||||||
|
if ($this->passphraseSSHKey !== null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$credential = id(new PassphraseCredentialQuery())
|
||||||
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||||
|
->withIDs(array($this->getConfig('credential')))
|
||||||
|
->needSecrets(true)
|
||||||
|
->executeOne();
|
||||||
|
|
||||||
|
if ($credential->getProvidesType() !==
|
||||||
|
PassphraseCredentialTypeSSHPrivateKey::PROVIDES_TYPE) {
|
||||||
|
throw new Exception("Only private key credentials are supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->passphraseSSHKey = PassphraseSSHKey::loadFromPHID(
|
||||||
|
$credential->getPHID(),
|
||||||
|
PhabricatorUser::getOmnipotentUser());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getExecFuture($path) {
|
||||||
|
$this->openCredentialsIfNotOpen();
|
||||||
|
|
||||||
|
return new ExecFuture(
|
||||||
|
'sftp -o "StrictHostKeyChecking no" -P %s -i %P %P@%s',
|
||||||
|
$this->getConfig('port'),
|
||||||
|
$this->passphraseSSHKey->getKeyfileEnvelope(),
|
||||||
|
$this->passphraseSSHKey->getUsernameEnvelope(),
|
||||||
|
$this->getConfig('host'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function readFile($path) {
|
||||||
|
$target = new TempFile();
|
||||||
|
$future = $this->getExecFuture($path);
|
||||||
|
$future->write(csprintf("get %s %s", $path, $target));
|
||||||
|
$future->resolvex();
|
||||||
|
return Filesystem::readFile($target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveFile($path, $name) {
|
||||||
|
$data = $this->readFile($path);
|
||||||
|
$file = PhabricatorFile::newFromFileData($data);
|
||||||
|
$file->setName($name);
|
||||||
|
$file->save();
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function writeFile($path, $data) {
|
||||||
|
$source = new TempFile();
|
||||||
|
Filesystem::writeFile($source, $data);
|
||||||
|
$future = $this->getExecFuture($path);
|
||||||
|
$future->write(csprintf("put %s %s", $source, $path));
|
||||||
|
$future->resolvex();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -56,7 +56,7 @@ final class DrydockBlueprintQuery
|
||||||
if ($this->phids) {
|
if ($this->phids) {
|
||||||
$where[] = qsprintf(
|
$where[] = qsprintf(
|
||||||
$conn_r,
|
$conn_r,
|
||||||
'phid IN (%Ld)',
|
'phid IN (%Ls)',
|
||||||
$this->phids);
|
$this->phids);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,30 +32,9 @@ final class CommandBuildStepImplementation
|
||||||
$settings['command'],
|
$settings['command'],
|
||||||
$variables);
|
$variables);
|
||||||
|
|
||||||
$artifact = id(new HarbormasterBuildArtifactQuery())
|
$artifact = $build->loadArtifact($settings['hostartifact']);
|
||||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
|
||||||
->withArtifactKeys(
|
|
||||||
$build->getPHID(),
|
|
||||||
array($settings['hostartifact']))
|
|
||||||
->executeOne();
|
|
||||||
if ($artifact === null) {
|
|
||||||
throw new Exception("Associated Drydock host artifact not found!");
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = $artifact->getArtifactData();
|
$lease = $artifact->loadDrydockLease();
|
||||||
|
|
||||||
// FIXME: Is there a better way of doing this?
|
|
||||||
$lease = id(new DrydockLease())->load(
|
|
||||||
$data['drydock-lease']);
|
|
||||||
if ($lease === null) {
|
|
||||||
throw new Exception("Associated Drydock lease not found!");
|
|
||||||
}
|
|
||||||
$resource = id(new DrydockResource())->load(
|
|
||||||
$lease->getResourceID());
|
|
||||||
if ($resource === null) {
|
|
||||||
throw new Exception("Associated Drydock resource not found!");
|
|
||||||
}
|
|
||||||
$lease->attachResource($resource);
|
|
||||||
|
|
||||||
$interface = $lease->getInterface('command');
|
$interface = $lease->getInterface('command');
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class UploadArtifactBuildStepImplementation
|
||||||
|
extends VariableBuildStepImplementation {
|
||||||
|
|
||||||
|
public function getName() {
|
||||||
|
return pht('Upload Artifact');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGenericDescription() {
|
||||||
|
return pht('Upload an artifact from a Drydock host to Phabricator.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription() {
|
||||||
|
$settings = $this->getSettings();
|
||||||
|
|
||||||
|
return pht(
|
||||||
|
'Upload artifact located at \'%s\' on \'%s\'.',
|
||||||
|
$settings['path'],
|
||||||
|
$settings['hostartifact']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(
|
||||||
|
HarbormasterBuild $build,
|
||||||
|
HarbormasterBuildTarget $build_target) {
|
||||||
|
|
||||||
|
$settings = $this->getSettings();
|
||||||
|
$variables = $build_target->getVariables();
|
||||||
|
|
||||||
|
$path = $this->mergeVariables(
|
||||||
|
'vsprintf',
|
||||||
|
$settings['path'],
|
||||||
|
$variables);
|
||||||
|
|
||||||
|
$artifact = $build->loadArtifact($settings['hostartifact']);
|
||||||
|
|
||||||
|
$lease = $artifact->loadDrydockLease();
|
||||||
|
|
||||||
|
$interface = $lease->getInterface('filesystem');
|
||||||
|
|
||||||
|
// TODO: Handle exceptions.
|
||||||
|
$file = $interface->saveFile($path, $settings['name']);
|
||||||
|
|
||||||
|
// Insert the artifact record.
|
||||||
|
$artifact = $build->createArtifact(
|
||||||
|
$build_target,
|
||||||
|
$settings['name'],
|
||||||
|
HarbormasterBuildArtifact::TYPE_FILE);
|
||||||
|
$artifact->setArtifactData(array(
|
||||||
|
'filePHID' => $file->getPHID()));
|
||||||
|
$artifact->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validateSettings() {
|
||||||
|
$settings = $this->getSettings();
|
||||||
|
|
||||||
|
if ($settings['path'] === null || !is_string($settings['path'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($settings['name'] === null || !is_string($settings['name'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($settings['hostartifact'] === null ||
|
||||||
|
!is_string($settings['hostartifact'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Check if the host artifact is provided by previous build steps.
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSettingDefinitions() {
|
||||||
|
return array(
|
||||||
|
'path' => array(
|
||||||
|
'name' => 'Path',
|
||||||
|
'description' =>
|
||||||
|
'The path of the file that should be retrieved. Note that on '.
|
||||||
|
'Windows machines running FreeSSHD, this path will be relative '.
|
||||||
|
'to the SFTP root path (configured under the SFTP tab). You can '.
|
||||||
|
'not specify an absolute path for Windows machines.',
|
||||||
|
'type' => BuildStepImplementation::SETTING_TYPE_STRING),
|
||||||
|
'name' => array(
|
||||||
|
'name' => 'Local Name',
|
||||||
|
'description' =>
|
||||||
|
'The name for the file when it is stored in Phabricator.',
|
||||||
|
'type' => BuildStepImplementation::SETTING_TYPE_STRING),
|
||||||
|
'hostartifact' => array(
|
||||||
|
'name' => 'Host Artifact',
|
||||||
|
'description' =>
|
||||||
|
'The host artifact that determines what machine the command '.
|
||||||
|
'will run on.',
|
||||||
|
'type' => BuildStepImplementation::SETTING_TYPE_ARTIFACT,
|
||||||
|
'artifact_type' => HarbormasterBuildArtifact::TYPE_HOST));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -119,6 +119,19 @@ final class HarbormasterBuild extends HarbormasterDAO
|
||||||
return $artifact;
|
return $artifact;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function loadArtifact($name) {
|
||||||
|
$artifact = id(new HarbormasterBuildArtifactQuery())
|
||||||
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||||
|
->withArtifactKeys(
|
||||||
|
$this->getPHID(),
|
||||||
|
array($name))
|
||||||
|
->executeOne();
|
||||||
|
if ($artifact === null) {
|
||||||
|
throw new Exception("Artifact not found!");
|
||||||
|
}
|
||||||
|
return $artifact;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks for and handles build cancellation. If this method returns
|
* Checks for and handles build cancellation. If this method returns
|
||||||
* true, the caller should stop any current operations and return control
|
* true, the caller should stop any current operations and return control
|
||||||
|
|
|
@ -72,6 +72,30 @@ final class HarbormasterBuildArtifact extends HarbormasterDAO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function loadDrydockLease() {
|
||||||
|
if ($this->getArtifactType() !== self::TYPE_HOST) {
|
||||||
|
throw new Exception(
|
||||||
|
"`loadDrydockLease` may only be called on host artifacts.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $this->getArtifactData();
|
||||||
|
|
||||||
|
// FIXME: Is there a better way of doing this?
|
||||||
|
$lease = id(new DrydockLease())->load(
|
||||||
|
$data['drydock-lease']);
|
||||||
|
if ($lease === null) {
|
||||||
|
throw new Exception("Associated Drydock lease not found!");
|
||||||
|
}
|
||||||
|
$resource = id(new DrydockResource())->load(
|
||||||
|
$lease->getResourceID());
|
||||||
|
if ($resource === null) {
|
||||||
|
throw new Exception("Associated Drydock resource not found!");
|
||||||
|
}
|
||||||
|
$lease->attachResource($resource);
|
||||||
|
|
||||||
|
return $lease;
|
||||||
|
}
|
||||||
|
|
||||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue