Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2891139
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
33 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment