1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-23 22:10:55 +01:00

Allow Harbormaster to lease working copies from Drydock

Summary: Ref T9252. This is still crude in a few ways but basically works, at least for commits.

Test Plan:
  - Made a build plan with just this build step.
  - Ran `bin/harbormaster build --plan 10 ...` on a commit.
  - It actually built a working copy, leased it, took no action, and released the lease. MAGIC~~~

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T9252

Differential Revision: https://secure.phabricator.com/D14160
This commit is contained in:
epriestley 2015-09-24 17:29:47 -07:00
parent 381fa611fd
commit 284fe0fe51
10 changed files with 225 additions and 74 deletions

View file

@ -1002,11 +1002,13 @@ 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',
'HarbormasterDrydockLeaseArtifact' => 'applications/harbormaster/artifact/HarbormasterDrydockLeaseArtifact.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',
'HarbormasterHostArtifact' => 'applications/harbormaster/artifact/HarbormasterHostArtifact.php', 'HarbormasterHostArtifact' => 'applications/harbormaster/artifact/HarbormasterHostArtifact.php',
'HarbormasterLeaseHostBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php', 'HarbormasterLeaseHostBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php',
'HarbormasterLeaseWorkingCopyBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php',
'HarbormasterLintMessagesController' => 'applications/harbormaster/controller/HarbormasterLintMessagesController.php', 'HarbormasterLintMessagesController' => 'applications/harbormaster/controller/HarbormasterLintMessagesController.php',
'HarbormasterLintPropertyView' => 'applications/harbormaster/view/HarbormasterLintPropertyView.php', 'HarbormasterLintPropertyView' => 'applications/harbormaster/view/HarbormasterLintPropertyView.php',
'HarbormasterManagePlansCapability' => 'applications/harbormaster/capability/HarbormasterManagePlansCapability.php', 'HarbormasterManagePlansCapability' => 'applications/harbormaster/capability/HarbormasterManagePlansCapability.php',
@ -1047,6 +1049,7 @@ phutil_register_library_map(array(
'HarbormasterUploadArtifactBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php', 'HarbormasterUploadArtifactBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php',
'HarbormasterWaitForPreviousBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php', 'HarbormasterWaitForPreviousBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php',
'HarbormasterWorker' => 'applications/harbormaster/worker/HarbormasterWorker.php', 'HarbormasterWorker' => 'applications/harbormaster/worker/HarbormasterWorker.php',
'HarbormasterWorkingCopyArtifact' => 'applications/harbormaster/artifact/HarbormasterWorkingCopyArtifact.php',
'HeraldAction' => 'applications/herald/action/HeraldAction.php', 'HeraldAction' => 'applications/herald/action/HeraldAction.php',
'HeraldActionGroup' => 'applications/herald/action/HeraldActionGroup.php', 'HeraldActionGroup' => 'applications/herald/action/HeraldActionGroup.php',
'HeraldActionRecord' => 'applications/herald/storage/HeraldActionRecord.php', 'HeraldActionRecord' => 'applications/herald/storage/HeraldActionRecord.php',
@ -4786,11 +4789,13 @@ phutil_register_library_map(array(
'HarbormasterController' => 'PhabricatorController', 'HarbormasterController' => 'PhabricatorController',
'HarbormasterCreateArtifactConduitAPIMethod' => 'HarbormasterConduitAPIMethod', 'HarbormasterCreateArtifactConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
'HarbormasterDAO' => 'PhabricatorLiskDAO', 'HarbormasterDAO' => 'PhabricatorLiskDAO',
'HarbormasterDrydockLeaseArtifact' => 'HarbormasterArtifact',
'HarbormasterExternalBuildStepGroup' => 'HarbormasterBuildStepGroup', 'HarbormasterExternalBuildStepGroup' => 'HarbormasterBuildStepGroup',
'HarbormasterFileArtifact' => 'HarbormasterArtifact', 'HarbormasterFileArtifact' => 'HarbormasterArtifact',
'HarbormasterHTTPRequestBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterHTTPRequestBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterHostArtifact' => 'HarbormasterArtifact', 'HarbormasterHostArtifact' => 'HarbormasterDrydockLeaseArtifact',
'HarbormasterLeaseHostBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterLeaseHostBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterLeaseWorkingCopyBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterLintMessagesController' => 'HarbormasterController', 'HarbormasterLintMessagesController' => 'HarbormasterController',
'HarbormasterLintPropertyView' => 'AphrontView', 'HarbormasterLintPropertyView' => 'AphrontView',
'HarbormasterManagePlansCapability' => 'PhabricatorPolicyCapability', 'HarbormasterManagePlansCapability' => 'PhabricatorPolicyCapability',
@ -4831,6 +4836,7 @@ phutil_register_library_map(array(
'HarbormasterUploadArtifactBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterUploadArtifactBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterWaitForPreviousBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterWaitForPreviousBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterWorker' => 'PhabricatorWorker', 'HarbormasterWorker' => 'PhabricatorWorker',
'HarbormasterWorkingCopyArtifact' => 'HarbormasterDrydockLeaseArtifact',
'HeraldAction' => 'Phobject', 'HeraldAction' => 'Phobject',
'HeraldActionGroup' => 'HeraldGroup', 'HeraldActionGroup' => 'HeraldGroup',
'HeraldActionRecord' => 'HeraldDAO', 'HeraldActionRecord' => 'HeraldDAO',

View file

@ -443,6 +443,7 @@ final class DifferentialDiff
if ($repo) { if ($repo) {
$results['repository.callsign'] = $repo->getCallsign(); $results['repository.callsign'] = $repo->getCallsign();
$results['repository.phid'] = $repo->getPHID();
$results['repository.vcs'] = $repo->getVersionControlSystem(); $results['repository.vcs'] = $repo->getVersionControlSystem();
$results['repository.uri'] = $repo->getPublicCloneURI(); $results['repository.uri'] = $repo->getPublicCloneURI();
} }
@ -459,6 +460,8 @@ final class DifferentialDiff
pht('The differential revision ID, if applicable.'), pht('The differential revision ID, if applicable.'),
'repository.callsign' => 'repository.callsign' =>
pht('The callsign of the repository in Phabricator.'), pht('The callsign of the repository in Phabricator.'),
'repository.phid' =>
pht('The PHID of the repository in Phabricator.'),
'repository.vcs' => 'repository.vcs' =>
pht('The version control system, either "svn", "hg" or "git".'), pht('The version control system, either "svn", "hg" or "git".'),
'repository.uri' => 'repository.uri' =>

View file

@ -126,22 +126,23 @@ final class DrydockLease extends DrydockDAO
return $this; return $this;
} }
public function isActive() { public function isActivating() {
switch ($this->status) { switch ($this->getStatus()) {
case DrydockLeaseStatus::STATUS_PENDING:
case DrydockLeaseStatus::STATUS_ACQUIRED: case DrydockLeaseStatus::STATUS_ACQUIRED:
case DrydockLeaseStatus::STATUS_ACTIVE:
return true; return true;
} }
return false; return false;
} }
private function assertActive() { public function isActive() {
if (!$this->isActive()) { switch ($this->getStatus()) {
throw new Exception( case DrydockLeaseStatus::STATUS_ACTIVE:
pht( return true;
'Lease is not active! You can not interact with resources through '.
'an inactive lease.'));
} }
return false;
} }
public function waitUntilActive() { public function waitUntilActive() {

View file

@ -41,7 +41,10 @@ final class DrydockLeaseListView extends AphrontView {
$item->addAttribute($status); $item->addAttribute($status);
$item->setEpoch($lease->getDateCreated()); $item->setEpoch($lease->getDateCreated());
if ($lease->isActive()) { // TODO: Tailor this for clarity.
if ($lease->isActivating()) {
$item->setStatusIcon('fa-dot-circle-o yellow');
} else if ($lease->isActive()) {
$item->setStatusIcon('fa-dot-circle-o green'); $item->setStatusIcon('fa-dot-circle-o green');
} else { } else {
$item->setStatusIcon('fa-dot-circle-o red'); $item->setStatusIcon('fa-dot-circle-o red');

View file

@ -0,0 +1,73 @@
<?php
abstract class HarbormasterDrydockLeaseArtifact
extends HarbormasterArtifact {
public function getArtifactParameterSpecification() {
return array(
'drydockLeasePHID' => 'string',
);
}
public function getArtifactParameterDescriptions() {
return array(
'drydockLeasePHID' => pht(
'Drydock working copy lease to create an artifact from.'),
);
}
public function getArtifactDataExample() {
return array(
'drydockLeasePHID' => 'PHID-DRYL-abcdefghijklmnopqrst',
);
}
public function renderArtifactSummary(PhabricatorUser $viewer) {
$artifact = $this->getBuildArtifact();
$lease_phid = $artifact->getProperty('drydockLeasePHID');
return $viewer->renderHandle($lease_phid);
}
public function willCreateArtifact(PhabricatorUser $actor) {
$this->loadArtifactLease($actor);
}
public function loadArtifactLease(PhabricatorUser $viewer) {
$artifact = $this->getBuildArtifact();
$lease_phid = $artifact->getProperty('drydockLeasePHID');
$lease = id(new DrydockLeaseQuery())
->setViewer($viewer)
->withPHIDs(array($lease_phid))
->executeOne();
if (!$lease) {
throw new Exception(
pht(
'Drydock lease PHID "%s" does not correspond to a valid lease.',
$lease_phid));
}
return $lease;
}
public function releaseArtifact(PhabricatorUser $actor) {
$lease = $this->loadArtifactLease($actor);
if (!$lease->canRelease()) {
return;
}
$author_phid = $actor->getPHID();
if (!$author_phid) {
$author_phid = id(new PhabricatorHarbormasterApplication())->getPHID();
}
$command = DrydockCommand::initializeNewCommand($actor)
->setTargetPHID($lease->getPHID())
->setAuthorPHID($author_phid)
->setCommand(DrydockCommand::COMMAND_RELEASE)
->save();
$lease->scheduleUpdate();
}
}

View file

@ -1,6 +1,7 @@
<?php <?php
final class HarbormasterHostArtifact extends HarbormasterArtifact { final class HarbormasterHostArtifact
extends HarbormasterDrydockLeaseArtifact {
const ARTIFACTCONST = 'host'; const ARTIFACTCONST = 'host';
@ -12,66 +13,4 @@ final class HarbormasterHostArtifact extends HarbormasterArtifact {
return pht('References a host lease from Drydock.'); return pht('References a host lease from Drydock.');
} }
public function getArtifactParameterSpecification() {
return array(
'drydockLeasePHID' => 'string',
);
}
public function getArtifactParameterDescriptions() {
return array(
'drydockLeasePHID' => pht(
'Drydock host lease to create an artifact from.'),
);
}
public function getArtifactDataExample() {
return array(
'drydockLeasePHID' => 'PHID-DRYL-abcdefghijklmnopqrst',
);
}
public function renderArtifactSummary(PhabricatorUser $viewer) {
$artifact = $this->getBuildArtifact();
$file_phid = $artifact->getProperty('drydockLeasePHID');
return $viewer->renderHandle($file_phid);
}
public function willCreateArtifact(PhabricatorUser $actor) {
$this->loadArtifactLease($actor);
}
public function loadArtifactLease(PhabricatorUser $viewer) {
$artifact = $this->getBuildArtifact();
$lease_phid = $artifact->getProperty('drydockLeasePHID');
$lease = id(new DrydockLeaseQuery())
->setViewer($viewer)
->withPHIDs(array($lease_phid))
->executeOne();
if (!$lease) {
throw new Exception(
pht(
'Drydock lease PHID "%s" does not correspond to a valid lease.',
$lease_phid));
}
return $lease;
}
public function releaseArtifact(PhabricatorUser $actor) {
$lease = $this->loadArtifactLease($actor);
if (!$lease->canRelease()) {
return;
}
$command = DrydockCommand::initializeNewCommand($actor)
->setTargetPHID($lease->getPHID())
->setCommand(DrydockCommand::COMMAND_RELEASE)
->save();
$lease->scheduleUpdate();
}
} }

View file

@ -0,0 +1,16 @@
<?php
final class HarbormasterWorkingCopyArtifact
extends HarbormasterDrydockLeaseArtifact {
const ARTIFACTCONST = 'working-copy';
public function getArtifactTypeName() {
return pht('Drydock Working Copy');
}
public function getArtifactTypeDescription() {
return pht('References a working copy lease from Drydock.');
}
}

View file

@ -0,0 +1,106 @@
<?php
final class HarbormasterLeaseWorkingCopyBuildStepImplementation
extends HarbormasterBuildStepImplementation {
public function getName() {
return pht('Lease Working Copy');
}
public function getGenericDescription() {
return pht('Build a working copy in Drydock.');
}
public function getBuildStepGroupKey() {
return HarbormasterPrototypeBuildStepGroup::GROUPKEY;
}
public function execute(
HarbormasterBuild $build,
HarbormasterBuildTarget $build_target) {
$viewer = PhabricatorUser::getOmnipotentUser();
$settings = $this->getSettings();
// TODO: We should probably have a separate temporary storage area for
// execution stuff that doesn't step on configuration state?
$lease_phid = $build_target->getDetail('exec.leasePHID');
if ($lease_phid) {
$lease = id(new DrydockLeaseQuery())
->setViewer($viewer)
->withPHIDs(array($lease_phid))
->executeOne();
if (!$lease) {
throw new PhabricatorWorkerPermanentFailureException(
pht(
'Lease "%s" could not be loaded.',
$lease_phid));
}
} else {
$working_copy_type = id(new DrydockWorkingCopyBlueprintImplementation())
->getType();
$lease = id(new DrydockLease())
->setResourceType($working_copy_type)
->setOwnerPHID($build_target->getPHID());
$variables = $build_target->getVariables();
$repository_phid = idx($variables, 'repository.phid');
$commit = idx($variables, 'repository.commit');
$lease
->setAttribute('repositoryPHID', $repository_phid)
->setAttribute('commit', $commit);
$lease->queueForActivation();
$build_target
->setDetail('exec.leasePHID', $lease->getPHID())
->save();
}
if ($lease->isActivating()) {
// TODO: Smart backoff?
throw new PhabricatorWorkerYieldException(15);
}
if (!$lease->isActive()) {
// TODO: We could just forget about this lease and retry?
throw new PhabricatorWorkerPermanentFailureException(
pht(
'Lease "%s" never activated.',
$lease->getPHID()));
}
$artifact = $build_target->createArtifact(
$viewer,
$settings['name'],
HarbormasterWorkingCopyArtifact::ARTIFACTCONST,
array(
'drydockLeasePHID' => $lease->getPHID(),
));
}
public function getArtifactOutputs() {
return array(
array(
'name' => pht('Working Copy'),
'key' => $this->getSetting('name'),
'type' => HarbormasterHostArtifact::ARTIFACTCONST,
),
);
}
public function getFieldSpecifications() {
return array(
'name' => array(
'name' => pht('Artifact Name'),
'type' => 'text',
'required' => true,
),
);
}
}

View file

@ -251,6 +251,7 @@ final class HarbormasterBuild extends HarbormasterDAO
'buildable.revision' => null, 'buildable.revision' => null,
'buildable.commit' => null, 'buildable.commit' => null,
'repository.callsign' => null, 'repository.callsign' => null,
'repository.phid' => null,
'repository.vcs' => null, 'repository.vcs' => null,
'repository.uri' => null, 'repository.uri' => null,
'step.timestamp' => null, 'step.timestamp' => null,

View file

@ -332,6 +332,7 @@ final class PhabricatorRepositoryCommit
$repo = $this->getRepository(); $repo = $this->getRepository();
$results['repository.callsign'] = $repo->getCallsign(); $results['repository.callsign'] = $repo->getCallsign();
$results['repository.phid'] = $repo->getPHID();
$results['repository.vcs'] = $repo->getVersionControlSystem(); $results['repository.vcs'] = $repo->getVersionControlSystem();
$results['repository.uri'] = $repo->getPublicCloneURI(); $results['repository.uri'] = $repo->getPublicCloneURI();
@ -343,6 +344,8 @@ final class PhabricatorRepositoryCommit
'buildable.commit' => pht('The commit identifier, if applicable.'), 'buildable.commit' => pht('The commit identifier, if applicable.'),
'repository.callsign' => 'repository.callsign' =>
pht('The callsign of the repository in Phabricator.'), pht('The callsign of the repository in Phabricator.'),
'repository.phid' =>
pht('The PHID of the repository in Phabricator.'),
'repository.vcs' => 'repository.vcs' =>
pht('The version control system, either "svn", "hg" or "git".'), pht('The version control system, either "svn", "hg" or "git".'),
'repository.uri' => 'repository.uri' =>