Page MenuHomePhorge

No OneTemporary

diff --git a/resources/sql/autopatches/20160617.harbormaster.01.arelease.sql b/resources/sql/autopatches/20160617.harbormaster.01.arelease.sql
new file mode 100644
index 0000000000..6f067d1549
--- /dev/null
+++ b/resources/sql/autopatches/20160617.harbormaster.01.arelease.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_buildartifact
+ ADD isReleased BOOL NOT NULL;
diff --git a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php
index 75a078a9d7..273f899f91 100644
--- a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php
+++ b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php
@@ -1,499 +1,568 @@
<?php
/**
* Moves a build forward by queuing build tasks, canceling or restarting the
* build, or failing it in response to task failures.
*/
final class HarbormasterBuildEngine extends Phobject {
private $build;
private $viewer;
private $newBuildTargets = array();
+ private $artifactReleaseQueue = array();
private $forceBuildableUpdate;
public function setForceBuildableUpdate($force_buildable_update) {
$this->forceBuildableUpdate = $force_buildable_update;
return $this;
}
public function shouldForceBuildableUpdate() {
return $this->forceBuildableUpdate;
}
public function queueNewBuildTarget(HarbormasterBuildTarget $target) {
$this->newBuildTargets[] = $target;
return $this;
}
public function getNewBuildTargets() {
return $this->newBuildTargets;
}
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setBuild(HarbormasterBuild $build) {
$this->build = $build;
return $this;
}
public function getBuild() {
return $this->build;
}
public function continueBuild() {
$build = $this->getBuild();
$lock_key = 'harbormaster.build:'.$build->getID();
$lock = PhabricatorGlobalLock::newLock($lock_key)->lock(15);
$build->reload();
$old_status = $build->getBuildStatus();
try {
$this->updateBuild($build);
} catch (Exception $ex) {
// If any exception is raised, the build is marked as a failure and the
// exception is re-thrown (this ensures we don't leave builds in an
// inconsistent state).
$build->setBuildStatus(HarbormasterBuild::STATUS_ERROR);
$build->save();
$lock->unlock();
$this->releaseAllArtifacts($build);
throw $ex;
}
$lock->unlock();
// NOTE: We queue new targets after releasing the lock so that in-process
// execution via `bin/harbormaster` does not reenter the locked region.
foreach ($this->getNewBuildTargets() as $target) {
$task = PhabricatorWorker::scheduleTask(
'HarbormasterTargetWorker',
array(
'targetID' => $target->getID(),
),
array(
'objectPHID' => $target->getPHID(),
));
}
// If the build changed status, we might need to update the overall status
// on the buildable.
$new_status = $build->getBuildStatus();
if ($new_status != $old_status || $this->shouldForceBuildableUpdate()) {
$this->updateBuildable($build->getBuildable());
}
+ $this->releaseQueuedArtifacts();
+
// If we are no longer building for any reason, release all artifacts.
if (!$build->isBuilding()) {
$this->releaseAllArtifacts($build);
}
}
private function updateBuild(HarbormasterBuild $build) {
if ($build->isAborting()) {
$this->releaseAllArtifacts($build);
$build->setBuildStatus(HarbormasterBuild::STATUS_ABORTED);
$build->save();
}
if (($build->getBuildStatus() == HarbormasterBuild::STATUS_PENDING) ||
($build->isRestarting())) {
$this->restartBuild($build);
$build->setBuildStatus(HarbormasterBuild::STATUS_BUILDING);
$build->save();
}
if ($build->isResuming()) {
$build->setBuildStatus(HarbormasterBuild::STATUS_BUILDING);
$build->save();
}
if ($build->isPausing() && !$build->isComplete()) {
$build->setBuildStatus(HarbormasterBuild::STATUS_PAUSED);
$build->save();
}
$build->deleteUnprocessedCommands();
if ($build->getBuildStatus() == HarbormasterBuild::STATUS_BUILDING) {
$this->updateBuildSteps($build);
}
}
private function restartBuild(HarbormasterBuild $build) {
// We're restarting the build, so release all previous artifacts.
$this->releaseAllArtifacts($build);
// Increment the build generation counter on the build.
$build->setBuildGeneration($build->getBuildGeneration() + 1);
// Currently running targets should periodically check their build
// generation (which won't have changed) against the build's generation.
// If it is different, they will automatically stop what they're doing
// and abort.
// Previously we used to delete targets, logs and artifacts here. Instead
// leave them around so users can view previous generations of this build.
}
private function updateBuildSteps(HarbormasterBuild $build) {
- $targets = id(new HarbormasterBuildTargetQuery())
+ $all_targets = id(new HarbormasterBuildTargetQuery())
->setViewer($this->getViewer())
->withBuildPHIDs(array($build->getPHID()))
->withBuildGenerations(array($build->getBuildGeneration()))
->execute();
- $this->updateWaitingTargets($targets);
+ $this->updateWaitingTargets($all_targets);
- $targets = mgroup($targets, 'getBuildStepPHID');
+ $targets = mgroup($all_targets, 'getBuildStepPHID');
$steps = id(new HarbormasterBuildStepQuery())
->setViewer($this->getViewer())
->withBuildPlanPHIDs(array($build->getBuildPlan()->getPHID()))
->execute();
+ $steps = mpull($steps, null, 'getPHID');
// Identify steps which are in various states.
$queued = array();
$underway = array();
$waiting = array();
$complete = array();
$failed = array();
foreach ($steps as $step) {
$step_targets = idx($targets, $step->getPHID(), array());
if ($step_targets) {
$is_queued = false;
$is_underway = false;
foreach ($step_targets as $target) {
if ($target->isUnderway()) {
$is_underway = true;
break;
}
}
$is_waiting = false;
foreach ($step_targets as $target) {
if ($target->isWaiting()) {
$is_waiting = true;
break;
}
}
$is_complete = true;
foreach ($step_targets as $target) {
if (!$target->isComplete()) {
$is_complete = false;
break;
}
}
$is_failed = false;
foreach ($step_targets as $target) {
if ($target->isFailed()) {
$is_failed = true;
break;
}
}
} else {
$is_queued = true;
$is_underway = false;
$is_waiting = false;
$is_complete = false;
$is_failed = false;
}
if ($is_queued) {
$queued[$step->getPHID()] = true;
}
if ($is_underway) {
$underway[$step->getPHID()] = true;
}
if ($is_waiting) {
$waiting[$step->getPHID()] = true;
}
if ($is_complete) {
$complete[$step->getPHID()] = true;
}
if ($is_failed) {
$failed[$step->getPHID()] = true;
}
}
// If any step failed, fail the whole build, then bail.
if (count($failed)) {
$build->setBuildStatus(HarbormasterBuild::STATUS_FAILED);
$build->save();
return;
}
// If every step is complete, we're done with this build. Mark it passed
// and bail.
if (count($complete) == count($steps)) {
$build->setBuildStatus(HarbormasterBuild::STATUS_PASSED);
$build->save();
return;
}
+ // Release any artifacts which are not inputs to any remaining build
+ // step. We're done with these, so something else is free to use them.
+ $ongoing_phids = array_keys($queued + $waiting + $underway);
+ $ongoing_steps = array_select_keys($steps, $ongoing_phids);
+ $this->releaseUnusedArtifacts($all_targets, $ongoing_steps);
+
// Identify all the steps which are ready to run (because all their
// dependencies are complete).
$runnable = array();
foreach ($steps as $step) {
$dependencies = $step->getStepImplementation()->getDependencies($step);
if (isset($queued[$step->getPHID()])) {
$can_run = true;
foreach ($dependencies as $dependency) {
if (empty($complete[$dependency])) {
$can_run = false;
break;
}
}
if ($can_run) {
$runnable[] = $step;
}
}
}
if (!$runnable && !$waiting && !$underway) {
// This means the build is deadlocked, and the user has configured
// circular dependencies.
$build->setBuildStatus(HarbormasterBuild::STATUS_DEADLOCKED);
$build->save();
return;
}
foreach ($runnable as $runnable_step) {
$target = HarbormasterBuildTarget::initializeNewBuildTarget(
$build,
$runnable_step,
$build->retrieveVariablesFromBuild());
$target->save();
$this->queueNewBuildTarget($target);
}
}
+ /**
+ * Release any artifacts which aren't used by any running or waiting steps.
+ *
+ * This releases artifacts as soon as they're no longer used. This can be
+ * particularly relevant when a build uses multiple hosts since it returns
+ * hosts to the pool more quickly.
+ *
+ * @param list<HarbormasterBuildTarget> Targets in the build.
+ * @param list<HarbormasterBuildStep> List of running and waiting steps.
+ * @return void
+ */
+ private function releaseUnusedArtifacts(array $targets, array $steps) {
+ assert_instances_of($targets, 'HarbormasterBuildTarget');
+ assert_instances_of($steps, 'HarbormasterBuildStep');
+
+ if (!$targets || !$steps) {
+ return;
+ }
+
+ $target_phids = mpull($targets, 'getPHID');
+
+ $artifacts = id(new HarbormasterBuildArtifactQuery())
+ ->setViewer($this->getViewer())
+ ->withBuildTargetPHIDs($target_phids)
+ ->withIsReleased(false)
+ ->execute();
+ if (!$artifacts) {
+ return;
+ }
+
+ // Collect all the artifacts that remaining build steps accept as inputs.
+ $must_keep = array();
+ foreach ($steps as $step) {
+ $inputs = $step->getStepImplementation()->getArtifactInputs();
+ foreach ($inputs as $input) {
+ $artifact_key = $input['key'];
+ $must_keep[$artifact_key] = true;
+ }
+ }
+
+ // Queue unreleased artifacts which no remaining step uses for immediate
+ // release.
+ foreach ($artifacts as $artifact) {
+ $key = $artifact->getArtifactKey();
+ if (isset($must_keep[$key])) {
+ continue;
+ }
+
+ $this->artifactReleaseQueue[] = $artifact;
+ }
+ }
+
+
/**
* Process messages which were sent to these targets, kicking applicable
* targets out of "Waiting" and into either "Passed" or "Failed".
*
* @param list<HarbormasterBuildTarget> List of targets to process.
* @return void
*/
private function updateWaitingTargets(array $targets) {
assert_instances_of($targets, 'HarbormasterBuildTarget');
// We only care about messages for targets which are actually in a waiting
// state.
$waiting_targets = array();
foreach ($targets as $target) {
if ($target->isWaiting()) {
$waiting_targets[$target->getPHID()] = $target;
}
}
if (!$waiting_targets) {
return;
}
$messages = id(new HarbormasterBuildMessageQuery())
->setViewer($this->getViewer())
->withBuildTargetPHIDs(array_keys($waiting_targets))
->withConsumed(false)
->execute();
foreach ($messages as $message) {
$target = $waiting_targets[$message->getBuildTargetPHID()];
switch ($message->getType()) {
case HarbormasterMessageType::MESSAGE_PASS:
$new_status = HarbormasterBuildTarget::STATUS_PASSED;
break;
case HarbormasterMessageType::MESSAGE_FAIL:
$new_status = HarbormasterBuildTarget::STATUS_FAILED;
break;
case HarbormasterMessageType::MESSAGE_WORK:
default:
$new_status = null;
break;
}
if ($new_status !== null) {
$message->setIsConsumed(true);
$message->save();
$target->setTargetStatus($new_status);
if ($target->isComplete()) {
$target->setDateCompleted(PhabricatorTime::getNow());
}
$target->save();
}
}
}
/**
* Update the overall status of the buildable this build is attached to.
*
* After a build changes state (for example, passes or fails) it may affect
* the overall state of the associated buildable. Compute the new aggregate
* state and save it on the buildable.
*
* @param HarbormasterBuild The buildable to update.
* @return void
*/
private function updateBuildable(HarbormasterBuildable $buildable) {
$viewer = $this->getViewer();
$lock_key = 'harbormaster.buildable:'.$buildable->getID();
$lock = PhabricatorGlobalLock::newLock($lock_key)->lock(15);
$buildable = id(new HarbormasterBuildableQuery())
->setViewer($viewer)
->withIDs(array($buildable->getID()))
->needBuilds(true)
->executeOne();
$all_pass = true;
$any_fail = false;
foreach ($buildable->getBuilds() as $build) {
if ($build->getBuildStatus() != HarbormasterBuild::STATUS_PASSED) {
$all_pass = false;
}
if ($build->getBuildStatus() == HarbormasterBuild::STATUS_FAILED ||
$build->getBuildStatus() == HarbormasterBuild::STATUS_ERROR ||
$build->getBuildStatus() == HarbormasterBuild::STATUS_DEADLOCKED) {
$any_fail = true;
}
}
if ($any_fail) {
$new_status = HarbormasterBuildable::STATUS_FAILED;
} else if ($all_pass) {
$new_status = HarbormasterBuildable::STATUS_PASSED;
} else {
$new_status = HarbormasterBuildable::STATUS_BUILDING;
}
$old_status = $buildable->getBuildableStatus();
$did_update = ($old_status != $new_status);
if ($did_update) {
$buildable->setBuildableStatus($new_status);
$buildable->save();
}
$lock->unlock();
// If we changed the buildable status, try to post a transaction to the
// object about it. We can safely do this outside of the locked region.
// NOTE: We only post transactions for automatic buildables, not for
// manual ones: manual builds are test builds, whoever is doing tests
// can look at the results themselves, and other users generally don't
// care about the outcome.
$should_publish = $did_update &&
$new_status != HarbormasterBuildable::STATUS_BUILDING &&
!$buildable->getIsManualBuildable();
if (!$should_publish) {
return;
}
$object = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withPHIDs(array($buildable->getBuildablePHID()))
->executeOne();
if (!$object) {
return;
}
if (!($object instanceof PhabricatorApplicationTransactionInterface)) {
return;
}
// TODO: Publishing these transactions is causing a race. See T8650.
// We shouldn't be publishing to diffs anyway.
if ($object instanceof DifferentialDiff) {
return;
}
$template = $object->getApplicationTransactionTemplate();
if (!$template) {
return;
}
$template
->setTransactionType(PhabricatorTransactions::TYPE_BUILDABLE)
->setMetadataValue(
'harbormaster:buildablePHID',
$buildable->getPHID())
->setOldValue($old_status)
->setNewValue($new_status);
$harbormaster_phid = id(new PhabricatorHarbormasterApplication())
->getPHID();
$daemon_source = PhabricatorContentSource::newForSource(
PhabricatorDaemonContentSource::SOURCECONST);
$editor = $object->getApplicationTransactionEditor()
->setActor($viewer)
->setActingAsPHID($harbormaster_phid)
->setContentSource($daemon_source)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
$editor->applyTransactions(
$object->getApplicationTransactionObject(),
array($template));
}
private function releaseAllArtifacts(HarbormasterBuild $build) {
$targets = id(new HarbormasterBuildTargetQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withBuildPHIDs(array($build->getPHID()))
->withBuildGenerations(array($build->getBuildGeneration()))
->execute();
if (count($targets) === 0) {
return;
}
$target_phids = mpull($targets, 'getPHID');
$artifacts = id(new HarbormasterBuildArtifactQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withBuildTargetPHIDs($target_phids)
+ ->withIsReleased(false)
->execute();
-
foreach ($artifacts as $artifact) {
$artifact->releaseArtifact();
}
+ }
+ private function releaseQueuedArtifacts() {
+ foreach ($this->artifactReleaseQueue as $key => $artifact) {
+ $artifact->releaseArtifact();
+ unset($this->artifactReleaseQueue[$key]);
+ }
}
}
diff --git a/src/applications/harbormaster/query/HarbormasterBuildArtifactQuery.php b/src/applications/harbormaster/query/HarbormasterBuildArtifactQuery.php
index 8f50aa1bb2..de35c05aa6 100644
--- a/src/applications/harbormaster/query/HarbormasterBuildArtifactQuery.php
+++ b/src/applications/harbormaster/query/HarbormasterBuildArtifactQuery.php
@@ -1,104 +1,117 @@
<?php
final class HarbormasterBuildArtifactQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $buildTargetPHIDs;
private $artifactTypes;
private $artifactIndexes;
private $keyBuildPHID;
private $keyBuildGeneration;
+ private $isReleased;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withBuildTargetPHIDs(array $build_target_phids) {
$this->buildTargetPHIDs = $build_target_phids;
return $this;
}
public function withArtifactTypes(array $artifact_types) {
$this->artifactTypes = $artifact_types;
return $this;
}
public function withArtifactIndexes(array $artifact_indexes) {
$this->artifactIndexes = $artifact_indexes;
return $this;
}
+ public function withIsReleased($released) {
+ $this->isReleased = $released;
+ return $this;
+ }
+
public function newResultObject() {
return new HarbormasterBuildArtifact();
}
protected function loadPage() {
return $this->loadStandardPage($this->newResultObject());
}
protected function willFilterPage(array $page) {
$build_targets = array();
$build_target_phids = array_filter(mpull($page, 'getBuildTargetPHID'));
if ($build_target_phids) {
$build_targets = id(new HarbormasterBuildTargetQuery())
->setViewer($this->getViewer())
->withPHIDs($build_target_phids)
->setParentQuery($this)
->execute();
$build_targets = mpull($build_targets, null, 'getPHID');
}
foreach ($page as $key => $build_log) {
$build_target_phid = $build_log->getBuildTargetPHID();
if (empty($build_targets[$build_target_phid])) {
unset($page[$key]);
continue;
}
$build_log->attachBuildTarget($build_targets[$build_target_phid]);
}
return $page;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->buildTargetPHIDs !== null) {
$where[] = qsprintf(
$conn,
'buildTargetPHID IN (%Ls)',
$this->buildTargetPHIDs);
}
if ($this->artifactTypes !== null) {
$where[] = qsprintf(
$conn,
'artifactType in (%Ls)',
$this->artifactTypes);
}
if ($this->artifactIndexes !== null) {
$where[] = qsprintf(
$conn,
'artifactIndex IN (%Ls)',
$this->artifactIndexes);
}
+ if ($this->isReleased !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'isReleased = %d',
+ (int)$this->isReleased);
+ }
+
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorHarbormasterApplication';
}
}
diff --git a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php
index 98da972a7d..4dac15f7a0 100644
--- a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php
+++ b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php
@@ -1,319 +1,314 @@
<?php
/**
* @task autotarget Automatic Targets
*/
abstract class HarbormasterBuildStepImplementation extends Phobject {
private $settings;
private $currentWorkerTaskID;
public function setCurrentWorkerTaskID($id) {
$this->currentWorkerTaskID = $id;
return $this;
}
public function getCurrentWorkerTaskID() {
return $this->currentWorkerTaskID;
}
public static function getImplementations() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->execute();
}
public static function getImplementation($class) {
$base = idx(self::getImplementations(), $class);
if ($base) {
return (clone $base);
}
return null;
}
public static function requireImplementation($class) {
if (!$class) {
throw new Exception(pht('No implementation is specified!'));
}
$implementation = self::getImplementation($class);
if (!$implementation) {
throw new Exception(pht('No such implementation "%s" exists!', $class));
}
return $implementation;
}
/**
* The name of the implementation.
*/
abstract public function getName();
public function getBuildStepGroupKey() {
return HarbormasterOtherBuildStepGroup::GROUPKEY;
}
/**
* The generic description of the implementation.
*/
public function getGenericDescription() {
return '';
}
/**
* The description of the implementation, based on the current settings.
*/
public function getDescription() {
return $this->getGenericDescription();
}
public function getEditInstructions() {
return null;
}
/**
* Run the build target against the specified build.
*/
abstract public function execute(
HarbormasterBuild $build,
HarbormasterBuildTarget $build_target);
/**
* Gets the settings for this build step.
*/
public function getSettings() {
return $this->settings;
}
public function getSetting($key, $default = null) {
return idx($this->settings, $key, $default);
}
/**
* Loads the settings for this build step implementation from a build
* step or target.
*/
final public function loadSettings($build_object) {
$this->settings = $build_object->getDetails();
return $this;
}
/**
* Return the name of artifacts produced by this command.
*
- * Something like:
- *
- * return array(
- * 'some_name_input_by_user' => 'host');
- *
* Future steps will calculate all available artifact mappings
* before them and filter on the type.
*
* @return array The mappings of artifact names to their types.
*/
public function getArtifactInputs() {
return array();
}
public function getArtifactOutputs() {
return array();
}
public function getDependencies(HarbormasterBuildStep $build_step) {
$dependencies = $build_step->getDetail('dependsOn', array());
$inputs = $build_step->getStepImplementation()->getArtifactInputs();
$inputs = ipull($inputs, null, 'key');
$artifacts = $this->getAvailableArtifacts(
$build_step->getBuildPlan(),
$build_step,
null);
foreach ($artifacts as $key => $type) {
if (!array_key_exists($key, $inputs)) {
unset($artifacts[$key]);
}
}
$artifact_steps = ipull($artifacts, 'step');
$artifact_steps = mpull($artifact_steps, 'getPHID');
$dependencies = array_merge($dependencies, $artifact_steps);
return $dependencies;
}
/**
* Returns a list of all artifacts made available in the build plan.
*/
public static function getAvailableArtifacts(
HarbormasterBuildPlan $build_plan,
$current_build_step,
$artifact_type) {
$steps = id(new HarbormasterBuildStepQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withBuildPlanPHIDs(array($build_plan->getPHID()))
->execute();
$artifacts = array();
$artifact_arrays = array();
foreach ($steps as $step) {
if ($current_build_step !== null &&
$step->getPHID() === $current_build_step->getPHID()) {
continue;
}
$implementation = $step->getStepImplementation();
$array = $implementation->getArtifactOutputs();
$array = ipull($array, 'type', 'key');
foreach ($array as $name => $type) {
if ($type !== $artifact_type && $artifact_type !== null) {
continue;
}
$artifacts[$name] = array('type' => $type, 'step' => $step);
}
}
return $artifacts;
}
/**
* Convert a user-provided string with variables in it, like:
*
* ls ${dirname}
*
* ...into a string with variables merged into it safely:
*
* ls 'dir with spaces'
*
* @param string Name of a `vxsprintf` function, like @{function:vcsprintf}.
* @param string User-provided pattern string containing `${variables}`.
* @param dict List of available replacement variables.
* @return string String with variables replaced safely into it.
*/
protected function mergeVariables($function, $pattern, array $variables) {
$regexp = '@\\$\\{(?P<name>[a-z\\./-]+)\\}@';
$matches = null;
preg_match_all($regexp, $pattern, $matches);
$argv = array();
foreach ($matches['name'] as $name) {
if (!array_key_exists($name, $variables)) {
throw new Exception(pht("No such variable '%s'!", $name));
}
$argv[] = $variables[$name];
}
$pattern = str_replace('%', '%%', $pattern);
$pattern = preg_replace($regexp, '%s', $pattern);
return call_user_func($function, $pattern, $argv);
}
public function getFieldSpecifications() {
return array();
}
protected function formatSettingForDescription($key, $default = null) {
return $this->formatValueForDescription($this->getSetting($key, $default));
}
protected function formatValueForDescription($value) {
if (strlen($value)) {
return phutil_tag('strong', array(), $value);
} else {
return phutil_tag('em', array(), pht('(null)'));
}
}
public function supportsWaitForMessage() {
return false;
}
public function shouldWaitForMessage(HarbormasterBuildTarget $target) {
if (!$this->supportsWaitForMessage()) {
return false;
}
return (bool)$target->getDetail('builtin.wait-for-message');
}
protected function shouldAbort(
HarbormasterBuild $build,
HarbormasterBuildTarget $target) {
return $build->getBuildGeneration() !== $target->getBuildGeneration();
}
protected function resolveFutures(
HarbormasterBuild $build,
HarbormasterBuildTarget $target,
array $futures) {
$futures = new FutureIterator($futures);
foreach ($futures->setUpdateInterval(5) as $key => $future) {
if ($future === null) {
$build->reload();
if ($this->shouldAbort($build, $target)) {
throw new HarbormasterBuildAbortedException();
}
}
}
}
protected function logHTTPResponse(
HarbormasterBuild $build,
HarbormasterBuildTarget $build_target,
BaseHTTPFuture $future,
$label) {
list($status, $body, $headers) = $future->resolve();
$header_lines = array();
// TODO: We don't currently preserve the entire "HTTP" response header, but
// should. Once we do, reproduce it here faithfully.
$status_code = $status->getStatusCode();
$header_lines[] = "HTTP {$status_code}";
foreach ($headers as $header) {
list($head, $tail) = $header;
$header_lines[] = "{$head}: {$tail}";
}
$header_lines = implode("\n", $header_lines);
$build_target
->newLog($label, 'http.head')
->append($header_lines);
$build_target
->newLog($label, 'http.body')
->append($body);
}
/* -( Automatic Targets )-------------------------------------------------- */
public function getBuildStepAutotargetStepKey() {
return null;
}
public function getBuildStepAutotargetPlanKey() {
throw new PhutilMethodNotImplementedException();
}
public function shouldRequireAutotargeting() {
return false;
}
}
diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php b/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php
index bb5f8382ba..6825bf6646 100644
--- a/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php
+++ b/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php
@@ -1,140 +1,147 @@
<?php
final class HarbormasterBuildArtifact extends HarbormasterDAO
implements PhabricatorPolicyInterface {
protected $buildTargetPHID;
protected $artifactType;
protected $artifactIndex;
protected $artifactKey;
protected $artifactData = array();
+ protected $isReleased = 0;
private $buildTarget = self::ATTACHABLE;
private $artifactImplementation;
public static function initializeNewBuildArtifact(
HarbormasterBuildTarget $build_target) {
return id(new HarbormasterBuildArtifact())
->attachBuildTarget($build_target)
->setBuildTargetPHID($build_target->getPHID());
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'artifactData' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'artifactType' => 'text32',
'artifactIndex' => 'bytes12',
'artifactKey' => 'text255',
+ 'isReleased' => 'bool',
),
self::CONFIG_KEY_SCHEMA => array(
'key_artifact' => array(
'columns' => array('artifactType', 'artifactIndex'),
'unique' => true,
),
'key_garbagecollect' => array(
'columns' => array('artifactType', 'dateCreated'),
),
'key_target' => array(
'columns' => array('buildTargetPHID', 'artifactType'),
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
HarbormasterBuildArtifactPHIDType::TYPECONST);
}
public function attachBuildTarget(HarbormasterBuildTarget $build_target) {
$this->buildTarget = $build_target;
return $this;
}
public function getBuildTarget() {
return $this->assertAttached($this->buildTarget);
}
public function setArtifactKey($key) {
$target = $this->getBuildTarget();
$this->artifactIndex = self::getArtifactIndex($target, $key);
$this->artifactKey = $key;
return $this;
}
public static function getArtifactIndex(
HarbormasterBuildTarget $target,
$artifact_key) {
$build = $target->getBuild();
$parts = array(
$build->getPHID(),
$target->getBuildGeneration(),
$artifact_key,
);
$parts = implode("\0", $parts);
return PhabricatorHash::digestForIndex($parts);
}
public function releaseArtifact() {
- $impl = $this->getArtifactImplementation();
+ if ($this->getIsReleased()) {
+ return $this;
+ }
+ $impl = $this->getArtifactImplementation();
if ($impl) {
$impl->releaseArtifact(PhabricatorUser::getOmnipotentUser());
}
- return null;
+ return $this
+ ->setIsReleased(1)
+ ->save();
}
public function getArtifactImplementation() {
if ($this->artifactImplementation === null) {
$type = $this->getArtifactType();
$impl = HarbormasterArtifact::getArtifactType($type);
if (!$impl) {
return null;
}
$impl = clone $impl;
$impl->setBuildArtifact($this);
$this->artifactImplementation = $impl;
}
return $this->artifactImplementation;
}
public function getProperty($key, $default = null) {
return idx($this->artifactData, $key, $default);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
return $this->getBuildTarget()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getBuildTarget()->hasAutomaticCapability(
$capability,
$viewer);
}
public function describeAutomaticCapability($capability) {
return pht('Users must be able to see a buildable to see its artifacts.');
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 14:41 (3 w, 2 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1125608
Default Alt Text
(33 KB)

Event Timeline