@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+236 -24
+6
src/__phutil_library_map__.php
··· 642 642 'DrydockConstants' => 'applications/drydock/constants/DrydockConstants.php', 643 643 'DrydockController' => 'applications/drydock/controller/DrydockController.php', 644 644 'DrydockDAO' => 'applications/drydock/storage/DrydockDAO.php', 645 + 'DrydockFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockFilesystemInterface.php', 645 646 'DrydockInterface' => 'applications/drydock/interface/DrydockInterface.php', 646 647 'DrydockLease' => 'applications/drydock/storage/DrydockLease.php', 647 648 'DrydockLeaseListController' => 'applications/drydock/controller/DrydockLeaseListController.php', ··· 668 669 'DrydockResourceQuery' => 'applications/drydock/query/DrydockResourceQuery.php', 669 670 'DrydockResourceStatus' => 'applications/drydock/constants/DrydockResourceStatus.php', 670 671 'DrydockResourceViewController' => 'applications/drydock/controller/DrydockResourceViewController.php', 672 + 'DrydockSFTPFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockSFTPFilesystemInterface.php', 671 673 'DrydockSSHCommandInterface' => 'applications/drydock/interface/command/DrydockSSHCommandInterface.php', 672 674 'DrydockWebrootInterface' => 'applications/drydock/interface/webroot/DrydockWebrootInterface.php', 673 675 'DrydockWorkingCopyBlueprintImplementation' => 'applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php', ··· 2332 2334 'SleepBuildStepImplementation' => 'applications/harbormaster/step/SleepBuildStepImplementation.php', 2333 2335 'SlowvoteEmbedView' => 'applications/slowvote/view/SlowvoteEmbedView.php', 2334 2336 'SlowvoteRemarkupRule' => 'applications/slowvote/remarkup/SlowvoteRemarkupRule.php', 2337 + 'UploadArtifactBuildStepImplementation' => 'applications/harbormaster/step/UploadArtifactBuildStepImplementation.php', 2335 2338 'VariableBuildStepImplementation' => 'applications/harbormaster/step/VariableBuildStepImplementation.php', 2336 2339 ), 2337 2340 'function' => ··· 2987 2990 'DrydockCommandInterface' => 'DrydockInterface', 2988 2991 'DrydockController' => 'PhabricatorController', 2989 2992 'DrydockDAO' => 'PhabricatorLiskDAO', 2993 + 'DrydockFilesystemInterface' => 'DrydockInterface', 2990 2994 'DrydockLease' => 'DrydockDAO', 2991 2995 'DrydockLeaseListController' => 'DrydockController', 2992 2996 'DrydockLeaseQuery' => 'PhabricatorOffsetPagedQuery', ··· 3016 3020 'DrydockResourceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 3017 3021 'DrydockResourceStatus' => 'DrydockConstants', 3018 3022 'DrydockResourceViewController' => 'DrydockController', 3023 + 'DrydockSFTPFilesystemInterface' => 'DrydockFilesystemInterface', 3019 3024 'DrydockSSHCommandInterface' => 'DrydockCommandInterface', 3020 3025 'DrydockWebrootInterface' => 'DrydockInterface', 3021 3026 'DrydockWorkingCopyBlueprintImplementation' => 'DrydockBlueprintImplementation', ··· 4973 4978 'SleepBuildStepImplementation' => 'BuildStepImplementation', 4974 4979 'SlowvoteEmbedView' => 'AphrontView', 4975 4980 'SlowvoteRemarkupRule' => 'PhabricatorRemarkupRuleObject', 4981 + 'UploadArtifactBuildStepImplementation' => 'VariableBuildStepImplementation', 4976 4982 'VariableBuildStepImplementation' => 'BuildStepImplementation', 4977 4983 ), 4978 4984 ));
+6
src/applications/drydock/blueprint/DrydockPreallocatedHostBlueprintImplementation.php
··· 105 105 'port' => $resource->getAttribute('port'), 106 106 'credential' => $resource->getAttribute('credential'), 107 107 'platform' => $resource->getAttribute('platform'))); 108 + case 'filesystem': 109 + return id(new DrydockSFTPFilesystemInterface()) 110 + ->setConfiguration(array( 111 + 'host' => $resource->getAttribute('host'), 112 + 'port' => $resource->getAttribute('port'), 113 + 'credential' => $resource->getAttribute('credential'))); 108 114 } 109 115 110 116 throw new Exception("No interface of type '{$type}'.");
+24
src/applications/drydock/interface/filesystem/DrydockFilesystemInterface.php
··· 1 + <?php 2 + 3 + abstract class DrydockFilesystemInterface extends DrydockInterface { 4 + 5 + final public function getInterfaceType() { 6 + return 'filesystem'; 7 + } 8 + 9 + /** 10 + * Reads a file on the Drydock resource and returns the contents of the file. 11 + */ 12 + abstract public function readFile($path); 13 + 14 + /** 15 + * Reads a file on the Drydock resource and saves it as a PhabricatorFile. 16 + */ 17 + abstract public function saveFile($path, $name); 18 + 19 + /** 20 + * Writes a file to the Drydock resource. 21 + */ 22 + abstract public function writeFile($path, $data); 23 + 24 + }
+63
src/applications/drydock/interface/filesystem/DrydockSFTPFilesystemInterface.php
··· 1 + <?php 2 + 3 + final class DrydockSFTPFilesystemInterface extends DrydockFilesystemInterface { 4 + 5 + private $passphraseSSHKey; 6 + 7 + private function openCredentialsIfNotOpen() { 8 + if ($this->passphraseSSHKey !== null) { 9 + return; 10 + } 11 + 12 + $credential = id(new PassphraseCredentialQuery()) 13 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 14 + ->withIDs(array($this->getConfig('credential'))) 15 + ->needSecrets(true) 16 + ->executeOne(); 17 + 18 + if ($credential->getProvidesType() !== 19 + PassphraseCredentialTypeSSHPrivateKey::PROVIDES_TYPE) { 20 + throw new Exception("Only private key credentials are supported."); 21 + } 22 + 23 + $this->passphraseSSHKey = PassphraseSSHKey::loadFromPHID( 24 + $credential->getPHID(), 25 + PhabricatorUser::getOmnipotentUser()); 26 + } 27 + 28 + private function getExecFuture($path) { 29 + $this->openCredentialsIfNotOpen(); 30 + 31 + return new ExecFuture( 32 + 'sftp -o "StrictHostKeyChecking no" -P %s -i %P %P@%s', 33 + $this->getConfig('port'), 34 + $this->passphraseSSHKey->getKeyfileEnvelope(), 35 + $this->passphraseSSHKey->getUsernameEnvelope(), 36 + $this->getConfig('host')); 37 + } 38 + 39 + public function readFile($path) { 40 + $target = new TempFile(); 41 + $future = $this->getExecFuture($path); 42 + $future->write(csprintf("get %s %s", $path, $target)); 43 + $future->resolvex(); 44 + return Filesystem::readFile($target); 45 + } 46 + 47 + public function saveFile($path, $name) { 48 + $data = $this->readFile($path); 49 + $file = PhabricatorFile::newFromFileData($data); 50 + $file->setName($name); 51 + $file->save(); 52 + return $file; 53 + } 54 + 55 + public function writeFile($path, $data) { 56 + $source = new TempFile(); 57 + Filesystem::writeFile($source, $data); 58 + $future = $this->getExecFuture($path); 59 + $future->write(csprintf("put %s %s", $source, $path)); 60 + $future->resolvex(); 61 + } 62 + 63 + }
+1 -1
src/applications/drydock/query/DrydockBlueprintQuery.php
··· 56 56 if ($this->phids) { 57 57 $where[] = qsprintf( 58 58 $conn_r, 59 - 'phid IN (%Ld)', 59 + 'phid IN (%Ls)', 60 60 $this->phids); 61 61 } 62 62
+2 -23
src/applications/harbormaster/step/CommandBuildStepImplementation.php
··· 32 32 $settings['command'], 33 33 $variables); 34 34 35 - $artifact = id(new HarbormasterBuildArtifactQuery()) 36 - ->setViewer(PhabricatorUser::getOmnipotentUser()) 37 - ->withArtifactKeys( 38 - $build->getPHID(), 39 - array($settings['hostartifact'])) 40 - ->executeOne(); 41 - if ($artifact === null) { 42 - throw new Exception("Associated Drydock host artifact not found!"); 43 - } 35 + $artifact = $build->loadArtifact($settings['hostartifact']); 44 36 45 - $data = $artifact->getArtifactData(); 46 - 47 - // FIXME: Is there a better way of doing this? 48 - $lease = id(new DrydockLease())->load( 49 - $data['drydock-lease']); 50 - if ($lease === null) { 51 - throw new Exception("Associated Drydock lease not found!"); 52 - } 53 - $resource = id(new DrydockResource())->load( 54 - $lease->getResourceID()); 55 - if ($resource === null) { 56 - throw new Exception("Associated Drydock resource not found!"); 57 - } 58 - $lease->attachResource($resource); 37 + $lease = $artifact->loadDrydockLease(); 59 38 60 39 $interface = $lease->getInterface('command'); 61 40
+97
src/applications/harbormaster/step/UploadArtifactBuildStepImplementation.php
··· 1 + <?php 2 + 3 + final class UploadArtifactBuildStepImplementation 4 + extends VariableBuildStepImplementation { 5 + 6 + public function getName() { 7 + return pht('Upload Artifact'); 8 + } 9 + 10 + public function getGenericDescription() { 11 + return pht('Upload an artifact from a Drydock host to Phabricator.'); 12 + } 13 + 14 + public function getDescription() { 15 + $settings = $this->getSettings(); 16 + 17 + return pht( 18 + 'Upload artifact located at \'%s\' on \'%s\'.', 19 + $settings['path'], 20 + $settings['hostartifact']); 21 + } 22 + 23 + public function execute( 24 + HarbormasterBuild $build, 25 + HarbormasterBuildTarget $build_target) { 26 + 27 + $settings = $this->getSettings(); 28 + $variables = $build_target->getVariables(); 29 + 30 + $path = $this->mergeVariables( 31 + 'vsprintf', 32 + $settings['path'], 33 + $variables); 34 + 35 + $artifact = $build->loadArtifact($settings['hostartifact']); 36 + 37 + $lease = $artifact->loadDrydockLease(); 38 + 39 + $interface = $lease->getInterface('filesystem'); 40 + 41 + // TODO: Handle exceptions. 42 + $file = $interface->saveFile($path, $settings['name']); 43 + 44 + // Insert the artifact record. 45 + $artifact = $build->createArtifact( 46 + $build_target, 47 + $settings['name'], 48 + HarbormasterBuildArtifact::TYPE_FILE); 49 + $artifact->setArtifactData(array( 50 + 'filePHID' => $file->getPHID())); 51 + $artifact->save(); 52 + } 53 + 54 + public function validateSettings() { 55 + $settings = $this->getSettings(); 56 + 57 + if ($settings['path'] === null || !is_string($settings['path'])) { 58 + return false; 59 + } 60 + if ($settings['name'] === null || !is_string($settings['name'])) { 61 + return false; 62 + } 63 + if ($settings['hostartifact'] === null || 64 + !is_string($settings['hostartifact'])) { 65 + return false; 66 + } 67 + 68 + // TODO: Check if the host artifact is provided by previous build steps. 69 + 70 + return true; 71 + } 72 + 73 + public function getSettingDefinitions() { 74 + return array( 75 + 'path' => array( 76 + 'name' => 'Path', 77 + 'description' => 78 + 'The path of the file that should be retrieved. Note that on '. 79 + 'Windows machines running FreeSSHD, this path will be relative '. 80 + 'to the SFTP root path (configured under the SFTP tab). You can '. 81 + 'not specify an absolute path for Windows machines.', 82 + 'type' => BuildStepImplementation::SETTING_TYPE_STRING), 83 + 'name' => array( 84 + 'name' => 'Local Name', 85 + 'description' => 86 + 'The name for the file when it is stored in Phabricator.', 87 + 'type' => BuildStepImplementation::SETTING_TYPE_STRING), 88 + 'hostartifact' => array( 89 + 'name' => 'Host Artifact', 90 + 'description' => 91 + 'The host artifact that determines what machine the command '. 92 + 'will run on.', 93 + 'type' => BuildStepImplementation::SETTING_TYPE_ARTIFACT, 94 + 'artifact_type' => HarbormasterBuildArtifact::TYPE_HOST)); 95 + } 96 + 97 + }
+13
src/applications/harbormaster/storage/build/HarbormasterBuild.php
··· 119 119 return $artifact; 120 120 } 121 121 122 + public function loadArtifact($name) { 123 + $artifact = id(new HarbormasterBuildArtifactQuery()) 124 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 125 + ->withArtifactKeys( 126 + $this->getPHID(), 127 + array($name)) 128 + ->executeOne(); 129 + if ($artifact === null) { 130 + throw new Exception("Artifact not found!"); 131 + } 132 + return $artifact; 133 + } 134 + 122 135 /** 123 136 * Checks for and handles build cancellation. If this method returns 124 137 * true, the caller should stop any current operations and return control
+24
src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php
··· 72 72 } 73 73 } 74 74 75 + public function loadDrydockLease() { 76 + if ($this->getArtifactType() !== self::TYPE_HOST) { 77 + throw new Exception( 78 + "`loadDrydockLease` may only be called on host artifacts."); 79 + } 80 + 81 + $data = $this->getArtifactData(); 82 + 83 + // FIXME: Is there a better way of doing this? 84 + $lease = id(new DrydockLease())->load( 85 + $data['drydock-lease']); 86 + if ($lease === null) { 87 + throw new Exception("Associated Drydock lease not found!"); 88 + } 89 + $resource = id(new DrydockResource())->load( 90 + $lease->getResourceID()); 91 + if ($resource === null) { 92 + throw new Exception("Associated Drydock resource not found!"); 93 + } 94 + $lease->attachResource($resource); 95 + 96 + return $lease; 97 + } 98 + 75 99 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 76 100 77 101