Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/harbormaster/constants/HarbormasterBuildStatus.php b/src/applications/harbormaster/constants/HarbormasterBuildStatus.php
index 3ceb8e0686..7fdc68d6dd 100644
--- a/src/applications/harbormaster/constants/HarbormasterBuildStatus.php
+++ b/src/applications/harbormaster/constants/HarbormasterBuildStatus.php
@@ -1,221 +1,302 @@
<?php
final class HarbormasterBuildStatus extends Phobject {
const STATUS_INACTIVE = 'inactive';
const STATUS_PENDING = 'pending';
const STATUS_BUILDING = 'building';
const STATUS_PASSED = 'passed';
const STATUS_FAILED = 'failed';
const STATUS_ABORTED = 'aborted';
const STATUS_ERROR = 'error';
const STATUS_PAUSED = 'paused';
const STATUS_DEADLOCKED = 'deadlocked';
+ const PENDING_PAUSING = 'x-pausing';
+ const PENDING_RESUMING = 'x-resuming';
+ const PENDING_RESTARTING = 'x-restarting';
+ const PENDING_ABORTING = 'x-aborting';
+
private $key;
private $properties;
public function __construct($key, array $properties) {
$this->key = $key;
$this->properties = $properties;
}
public static function newBuildStatusObject($status) {
$spec = self::getBuildStatusSpec($status);
return new self($status, $spec);
}
private function getProperty($key) {
if (!array_key_exists($key, $this->properties)) {
throw new Exception(
pht(
'Attempting to access unknown build status property ("%s").',
$key));
}
return $this->properties[$key];
}
public function isBuilding() {
return $this->getProperty('isBuilding');
}
public function isPaused() {
return ($this->key === self::STATUS_PAUSED);
}
public function isComplete() {
return $this->getProperty('isComplete');
}
+ public function isPending() {
+ return $this->getProperty('isPending');
+ }
+
public function isPassed() {
return ($this->key === self::STATUS_PASSED);
}
public function isFailed() {
return ($this->key === self::STATUS_FAILED);
}
+ public function isAborting() {
+ return ($this->key === self::PENDING_ABORTING);
+ }
+
+ public function isRestarting() {
+ return ($this->key === self::PENDING_RESTARTING);
+ }
+
+ public function isResuming() {
+ return ($this->key === self::PENDING_RESUMING);
+ }
+
+ public function isPausing() {
+ return ($this->key === self::PENDING_PAUSING);
+ }
+
+ public function getIconIcon() {
+ return $this->getProperty('icon');
+ }
+
+ public function getIconColor() {
+ return $this->getProperty('color');
+ }
+
+ public function getName() {
+ return $this->getProperty('name');
+ }
/**
* Get a human readable name for a build status constant.
*
* @param const Build status constant.
* @return string Human-readable name.
*/
public static function getBuildStatusName($status) {
$spec = self::getBuildStatusSpec($status);
return $spec['name'];
}
public static function getBuildStatusMap() {
$specs = self::getBuildStatusSpecMap();
return ipull($specs, 'name');
}
public static function getBuildStatusIcon($status) {
$spec = self::getBuildStatusSpec($status);
return $spec['icon'];
}
public static function getBuildStatusColor($status) {
$spec = self::getBuildStatusSpec($status);
return $spec['color'];
}
public static function getBuildStatusANSIColor($status) {
$spec = self::getBuildStatusSpec($status);
return $spec['color.ansi'];
}
public static function getWaitingStatusConstants() {
return array(
self::STATUS_INACTIVE,
self::STATUS_PENDING,
);
}
public static function getActiveStatusConstants() {
return array(
self::STATUS_BUILDING,
self::STATUS_PAUSED,
);
}
public static function getIncompleteStatusConstants() {
$map = self::getBuildStatusSpecMap();
$constants = array();
foreach ($map as $constant => $spec) {
if (!$spec['isComplete']) {
$constants[] = $constant;
}
}
return $constants;
}
public static function getCompletedStatusConstants() {
return array(
self::STATUS_PASSED,
self::STATUS_FAILED,
self::STATUS_ABORTED,
self::STATUS_ERROR,
self::STATUS_DEADLOCKED,
);
}
private static function getBuildStatusSpecMap() {
return array(
self::STATUS_INACTIVE => array(
'name' => pht('Inactive'),
'icon' => 'fa-circle-o',
'color' => 'dark',
'color.ansi' => 'yellow',
'isBuilding' => false,
'isComplete' => false,
+ 'isPending' => false,
),
self::STATUS_PENDING => array(
'name' => pht('Pending'),
'icon' => 'fa-circle-o',
'color' => 'blue',
'color.ansi' => 'yellow',
'isBuilding' => true,
'isComplete' => false,
+ 'isPending' => false,
),
self::STATUS_BUILDING => array(
'name' => pht('Building'),
'icon' => 'fa-chevron-circle-right',
'color' => 'blue',
'color.ansi' => 'yellow',
'isBuilding' => true,
'isComplete' => false,
+ 'isPending' => false,
),
self::STATUS_PASSED => array(
'name' => pht('Passed'),
'icon' => 'fa-check-circle',
'color' => 'green',
'color.ansi' => 'green',
'isBuilding' => false,
'isComplete' => true,
+ 'isPending' => false,
),
self::STATUS_FAILED => array(
'name' => pht('Failed'),
'icon' => 'fa-times-circle',
'color' => 'red',
'color.ansi' => 'red',
'isBuilding' => false,
'isComplete' => true,
+ 'isPending' => false,
),
self::STATUS_ABORTED => array(
'name' => pht('Aborted'),
'icon' => 'fa-minus-circle',
'color' => 'red',
'color.ansi' => 'red',
'isBuilding' => false,
'isComplete' => true,
+ 'isPending' => false,
),
self::STATUS_ERROR => array(
'name' => pht('Unexpected Error'),
'icon' => 'fa-minus-circle',
'color' => 'red',
'color.ansi' => 'red',
'isBuilding' => false,
'isComplete' => true,
+ 'isPending' => false,
),
self::STATUS_PAUSED => array(
'name' => pht('Paused'),
'icon' => 'fa-minus-circle',
'color' => 'dark',
'color.ansi' => 'yellow',
'isBuilding' => false,
'isComplete' => false,
+ 'isPending' => false,
),
self::STATUS_DEADLOCKED => array(
'name' => pht('Deadlocked'),
'icon' => 'fa-exclamation-circle',
'color' => 'red',
'color.ansi' => 'red',
'isBuilding' => false,
'isComplete' => true,
+ 'isPending' => false,
+ ),
+ self::PENDING_PAUSING => array(
+ 'name' => pht('Pausing'),
+ 'icon' => 'fa-exclamation-triangle',
+ 'color' => 'red',
+ 'color.ansi' => 'red',
+ 'isBuilding' => false,
+ 'isComplete' => false,
+ 'isPending' => true,
+ ),
+ self::PENDING_RESUMING => array(
+ 'name' => pht('Resuming'),
+ 'icon' => 'fa-exclamation-triangle',
+ 'color' => 'red',
+ 'color.ansi' => 'red',
+ 'isBuilding' => false,
+ 'isComplete' => false,
+ 'isPending' => true,
+ ),
+ self::PENDING_RESTARTING => array(
+ 'name' => pht('Restarting'),
+ 'icon' => 'fa-exclamation-triangle',
+ 'color' => 'red',
+ 'color.ansi' => 'red',
+ 'isBuilding' => false,
+ 'isComplete' => false,
+ 'isPending' => true,
+ ),
+ self::PENDING_ABORTING => array(
+ 'name' => pht('Aborting'),
+ 'icon' => 'fa-exclamation-triangle',
+ 'color' => 'red',
+ 'color.ansi' => 'red',
+ 'isBuilding' => false,
+ 'isComplete' => false,
+ 'isPending' => true,
),
);
}
private static function getBuildStatusSpec($status) {
$map = self::getBuildStatusSpecMap();
if (isset($map[$status])) {
return $map[$status];
}
return array(
'name' => pht('Unknown ("%s")', $status),
'icon' => 'fa-question-circle',
'color' => 'bluegrey',
'color.ansi' => 'magenta',
'isBuilding' => false,
'isComplete' => false,
);
}
}
diff --git a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php
index b0f2654ea6..970c01a564 100644
--- a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php
+++ b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php
@@ -1,780 +1,743 @@
<?php
final class HarbormasterBuildViewController
extends HarbormasterController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$request = $this->getRequest();
$viewer = $request->getUser();
$id = $request->getURIData('id');
$build = id(new HarbormasterBuildQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$build) {
return new Aphront404Response();
}
require_celerity_resource('harbormaster-css');
$title = pht('Build %d', $id);
$warnings = array();
$page_header = id(new PHUIHeaderView())
->setHeader($title)
->setUser($viewer)
->setPolicyObject($build)
->setHeaderIcon('fa-cubes');
- $is_restarting = $build->isRestarting();
-
- if ($is_restarting) {
- $page_header->setStatus(
- 'fa-exclamation-triangle', 'red', pht('Restarting'));
- } else if ($build->isPausing()) {
- $page_header->setStatus(
- 'fa-exclamation-triangle', 'red', pht('Pausing'));
- } else if ($build->isResuming()) {
- $page_header->setStatus(
- 'fa-exclamation-triangle', 'red', pht('Resuming'));
- } else if ($build->isAborting()) {
- $page_header->setStatus(
- 'fa-exclamation-triangle', 'red', pht('Aborting'));
- }
+ $status = $build->getBuildPendingStatusObject();
+
+ $status_icon = $status->getIconIcon();
+ $status_color = $status->getIconColor();
+ $status_name = $status->getName();
+
+ $page_header->setStatus($status_icon, $status_color, $status_name);
$max_generation = (int)$build->getBuildGeneration();
if ($max_generation === 0) {
$min_generation = 0;
} else {
$min_generation = 1;
}
- if ($is_restarting) {
+ if ($build->isRestarting()) {
$max_generation = $max_generation + 1;
}
$generation = $request->getURIData('generation');
if ($generation === null) {
$generation = $max_generation;
} else {
$generation = (int)$generation;
}
if ($generation < $min_generation || $generation > $max_generation) {
return new Aphront404Response();
}
if ($generation < $max_generation) {
$warnings[] = pht(
'You are viewing an older run of this build. %s',
phutil_tag(
'a',
array(
'href' => $build->getURI(),
),
pht('View Current Build')));
}
$curtain = $this->buildCurtainView($build);
$properties = $this->buildPropertyList($build);
$history = $this->buildHistoryTable(
$build,
$generation,
$min_generation,
$max_generation);
$crumbs = $this->buildApplicationCrumbs();
$this->addBuildableCrumb($crumbs, $build->getBuildable());
$crumbs->addTextCrumb($title);
$crumbs->setBorder(true);
$build_targets = id(new HarbormasterBuildTargetQuery())
->setViewer($viewer)
->needBuildSteps(true)
->withBuildPHIDs(array($build->getPHID()))
->withBuildGenerations(array($generation))
->execute();
if ($build_targets) {
$messages = id(new HarbormasterBuildMessageQuery())
->setViewer($viewer)
->withReceiverPHIDs(mpull($build_targets, 'getPHID'))
->execute();
$messages = mgroup($messages, 'getReceiverPHID');
} else {
$messages = array();
}
if ($build_targets) {
$artifacts = id(new HarbormasterBuildArtifactQuery())
->setViewer($viewer)
->withBuildTargetPHIDs(mpull($build_targets, 'getPHID'))
->execute();
$artifacts = msort($artifacts, 'getArtifactKey');
$artifacts = mgroup($artifacts, 'getBuildTargetPHID');
} else {
$artifacts = array();
}
$targets = array();
foreach ($build_targets as $build_target) {
$header = id(new PHUIHeaderView())
->setHeader($build_target->getName())
->setUser($viewer)
->setHeaderIcon('fa-bullseye');
$target_box = id(new PHUIObjectBoxView())
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setHeader($header);
$tab_group = new PHUITabGroupView();
$target_box->addTabGroup($tab_group);
$property_list = new PHUIPropertyListView();
$target_artifacts = idx($artifacts, $build_target->getPHID(), array());
$links = array();
$type_uri = HarbormasterURIArtifact::ARTIFACTCONST;
foreach ($target_artifacts as $artifact) {
if ($artifact->getArtifactType() == $type_uri) {
$impl = $artifact->getArtifactImplementation();
if ($impl->isExternalLink()) {
$links[] = $impl->renderLink();
}
}
}
if ($links) {
$links = phutil_implode_html(phutil_tag('br'), $links);
$property_list->addProperty(
pht('External Link'),
$links);
}
$status_view = new PHUIStatusListView();
$item = new PHUIStatusItemView();
$status = $build_target->getTargetStatus();
$status_name =
HarbormasterBuildTarget::getBuildTargetStatusName($status);
$icon = HarbormasterBuildTarget::getBuildTargetStatusIcon($status);
$color = HarbormasterBuildTarget::getBuildTargetStatusColor($status);
$item->setTarget($status_name);
$item->setIcon($icon, $color);
$status_view->addItem($item);
$when = array();
$started = $build_target->getDateStarted();
$now = PhabricatorTime::getNow();
if ($started) {
$ended = $build_target->getDateCompleted();
if ($ended) {
$when[] = pht(
'Completed at %s',
phabricator_datetime($ended, $viewer));
$duration = ($ended - $started);
if ($duration) {
$when[] = pht(
'Built for %s',
phutil_format_relative_time_detailed($duration));
} else {
$when[] = pht('Built instantly');
}
} else {
$when[] = pht(
'Started at %s',
phabricator_datetime($started, $viewer));
$duration = ($now - $started);
if ($duration) {
$when[] = pht(
'Running for %s',
phutil_format_relative_time_detailed($duration));
}
}
} else {
$created = $build_target->getDateCreated();
$when[] = pht(
'Queued at %s',
phabricator_datetime($started, $viewer));
$duration = ($now - $created);
if ($duration) {
$when[] = pht(
'Waiting for %s',
phutil_format_relative_time_detailed($duration));
}
}
$property_list->addProperty(
pht('When'),
phutil_implode_html(" \xC2\xB7 ", $when));
$property_list->addProperty(pht('Status'), $status_view);
$tab_group->addTab(
id(new PHUITabView())
->setName(pht('Overview'))
->setKey('overview')
->appendChild($property_list));
$step = $build_target->getBuildStep();
if ($step) {
$description = $step->getDescription();
if ($description) {
$description = new PHUIRemarkupView($viewer, $description);
$property_list->addSectionHeader(
pht('Description'), PHUIPropertyListView::ICON_SUMMARY);
$property_list->addTextContent($description);
}
} else {
$target_box->setFormErrors(
array(
pht(
'This build step has since been deleted on the build plan. '.
'Some information may be omitted.'),
));
}
$details = $build_target->getDetails();
$property_list = new PHUIPropertyListView();
foreach ($details as $key => $value) {
$property_list->addProperty($key, $value);
}
$tab_group->addTab(
id(new PHUITabView())
->setName(pht('Configuration'))
->setKey('configuration')
->appendChild($property_list));
$variables = $build_target->getVariables();
$variables_tab = $this->buildProperties($variables);
$tab_group->addTab(
id(new PHUITabView())
->setName(pht('Variables'))
->setKey('variables')
->appendChild($variables_tab));
$artifacts_tab = $this->buildArtifacts($build_target, $target_artifacts);
$tab_group->addTab(
id(new PHUITabView())
->setName(pht('Artifacts'))
->setKey('artifacts')
->appendChild($artifacts_tab));
$build_messages = idx($messages, $build_target->getPHID(), array());
$messages_tab = $this->buildMessages($build_messages);
$tab_group->addTab(
id(new PHUITabView())
->setName(pht('Messages'))
->setKey('messages')
->appendChild($messages_tab));
$property_list = new PHUIPropertyListView();
$property_list->addProperty(
pht('Build Target ID'),
$build_target->getID());
$property_list->addProperty(
pht('Build Target PHID'),
$build_target->getPHID());
$tab_group->addTab(
id(new PHUITabView())
->setName(pht('Metadata'))
->setKey('metadata')
->appendChild($property_list));
$targets[] = $target_box;
$targets[] = $this->buildLog($build, $build_target, $generation);
}
$timeline = $this->buildTransactionTimeline(
$build,
new HarbormasterBuildTransactionQuery());
$timeline->setShouldTerminate(true);
if ($warnings) {
$warnings = id(new PHUIInfoView())
->setErrors($warnings)
->setSeverity(PHUIInfoView::SEVERITY_WARNING);
} else {
$warnings = null;
}
$view = id(new PHUITwoColumnView())
->setHeader($page_header)
->setCurtain($curtain)
->setMainColumn(
array(
$warnings,
$properties,
$history,
$targets,
$timeline,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
private function buildArtifacts(
HarbormasterBuildTarget $build_target,
array $artifacts) {
$viewer = $this->getViewer();
$rows = array();
foreach ($artifacts as $artifact) {
$impl = $artifact->getArtifactImplementation();
if ($impl) {
$summary = $impl->renderArtifactSummary($viewer);
$type_name = $impl->getArtifactTypeName();
} else {
$summary = pht('<Unknown Artifact Type>');
$type_name = $artifact->getType();
}
$rows[] = array(
$artifact->getArtifactKey(),
$type_name,
$summary,
);
}
$table = id(new AphrontTableView($rows))
->setNoDataString(pht('This target has no associated artifacts.'))
->setHeaders(
array(
pht('Key'),
pht('Type'),
pht('Summary'),
))
->setColumnClasses(
array(
'pri',
'',
'wide',
));
return $table;
}
private function buildLog(
HarbormasterBuild $build,
HarbormasterBuildTarget $build_target,
$generation) {
$request = $this->getRequest();
$viewer = $request->getUser();
$limit = $request->getInt('l', 25);
$logs = id(new HarbormasterBuildLogQuery())
->setViewer($viewer)
->withBuildTargetPHIDs(array($build_target->getPHID()))
->execute();
$empty_logs = array();
$log_boxes = array();
foreach ($logs as $log) {
$start = 1;
$lines = preg_split("/\r\n|\r|\n/", $log->getLogText());
if ($limit !== 0) {
$start = count($lines) - $limit;
if ($start >= 1) {
$lines = array_slice($lines, -$limit, $limit);
} else {
$start = 1;
}
}
$id = null;
$is_empty = false;
if (count($lines) === 1 && trim($lines[0]) === '') {
// Prevent Harbormaster from showing empty build logs.
$id = celerity_generate_unique_node_id();
$empty_logs[] = $id;
$is_empty = true;
}
$log_view = new ShellLogView();
$log_view->setLines($lines);
$log_view->setStart($start);
$subheader = $this->createLogHeader($build, $log, $limit, $generation);
$prototype_view = id(new PHUIButtonView())
->setTag('a')
->setHref($log->getURI())
->setIcon('fa-file-text-o')
->setText(pht('New View (Prototype)'));
$header = id(new PHUIHeaderView())
->setHeader(pht(
'Build Log %d (%s - %s)',
$log->getID(),
$log->getLogSource(),
$log->getLogType()))
->addActionLink($prototype_view)
->setSubheader($subheader)
->setUser($viewer);
$log_box = id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setForm($log_view);
if ($is_empty) {
$log_box = phutil_tag(
'div',
array(
'style' => 'display: none',
'id' => $id,
),
$log_box);
}
$log_boxes[] = $log_box;
}
if ($empty_logs) {
$hide_id = celerity_generate_unique_node_id();
Javelin::initBehavior('phabricator-reveal-content');
$expand = phutil_tag(
'div',
array(
'id' => $hide_id,
'class' => 'harbormaster-empty-logs-are-hidden',
),
array(
pht(
'%s empty logs are hidden.',
phutil_count($empty_logs)),
' ',
javelin_tag(
'a',
array(
'href' => '#',
'sigil' => 'reveal-content',
'meta' => array(
'showIDs' => $empty_logs,
'hideIDs' => array($hide_id),
),
),
pht('Show all logs.')),
));
array_unshift($log_boxes, $expand);
}
return $log_boxes;
}
private function createLogHeader($build, $log, $limit, $generation) {
$options = array(
array(
'n' => 25,
),
array(
'n' => 50,
),
array(
'n' => 100,
),
array(
'n' => 0,
'label' => pht('Unlimited'),
),
);
$base_uri = id(new PhutilURI($build->getURI().$generation.'/'));
$links = array();
foreach ($options as $option) {
$n = $option['n'];
$label = idx($option, 'label', $n);
$is_selected = ($limit == $n);
if ($is_selected) {
$links[] = phutil_tag(
'strong',
array(),
$label);
} else {
$links[] = phutil_tag(
'a',
array(
'href' => (string)$base_uri->alter('l', $n),
),
$label);
}
}
return phutil_tag(
'span',
array(),
array(
phutil_implode_html(' - ', $links),
' ',
pht('Lines'),
));
}
private function buildCurtainView(HarbormasterBuild $build) {
$viewer = $this->getViewer();
$id = $build->getID();
$curtain = $this->newCurtainView($build);
$can_restart =
$build->canRestartBuild() &&
$build->canIssueCommand(
$viewer,
HarbormasterBuildCommand::COMMAND_RESTART);
$can_pause =
$build->canPauseBuild() &&
$build->canIssueCommand(
$viewer,
HarbormasterBuildCommand::COMMAND_PAUSE);
$can_resume =
$build->canResumeBuild() &&
$build->canIssueCommand(
$viewer,
HarbormasterBuildCommand::COMMAND_RESUME);
$can_abort =
$build->canAbortBuild() &&
$build->canIssueCommand(
$viewer,
HarbormasterBuildCommand::COMMAND_ABORT);
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Restart Build'))
->setIcon('fa-repeat')
->setHref($this->getApplicationURI('/build/restart/'.$id.'/'))
->setDisabled(!$can_restart)
->setWorkflow(true));
if ($build->canResumeBuild()) {
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Resume Build'))
->setIcon('fa-play')
->setHref($this->getApplicationURI('/build/resume/'.$id.'/'))
->setDisabled(!$can_resume)
->setWorkflow(true));
} else {
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Pause Build'))
->setIcon('fa-pause')
->setHref($this->getApplicationURI('/build/pause/'.$id.'/'))
->setDisabled(!$can_pause)
->setWorkflow(true));
}
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Abort Build'))
->setIcon('fa-exclamation-triangle')
->setHref($this->getApplicationURI('/build/abort/'.$id.'/'))
->setDisabled(!$can_abort)
->setWorkflow(true));
return $curtain;
}
private function buildPropertyList(HarbormasterBuild $build) {
$viewer = $this->getViewer();
$properties = id(new PHUIPropertyListView())
->setUser($viewer);
$handles = id(new PhabricatorHandleQuery())
->setViewer($viewer)
->withPHIDs(array(
$build->getBuildablePHID(),
$build->getBuildPlanPHID(),
))
->execute();
$properties->addProperty(
pht('Buildable'),
$handles[$build->getBuildablePHID()]->renderLink());
$properties->addProperty(
pht('Build Plan'),
$handles[$build->getBuildPlanPHID()]->renderLink());
- $properties->addProperty(
- pht('Status'),
- $this->getStatus($build));
-
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Properties'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($properties);
}
private function buildHistoryTable(
HarbormasterBuild $build,
$generation,
$min_generation,
$max_generation) {
if ($max_generation === $min_generation) {
return null;
}
$viewer = $this->getViewer();
$uri = $build->getURI();
$rows = array();
$rowc = array();
for ($ii = $max_generation; $ii >= $min_generation; $ii--) {
if ($generation == $ii) {
$rowc[] = 'highlighted';
} else {
$rowc[] = null;
}
$rows[] = array(
phutil_tag(
'a',
array(
'href' => $uri.$ii.'/',
),
pht('Run %d', $ii)),
);
}
$table = id(new AphrontTableView($rows))
->setColumnClasses(
array(
'pri wide',
))
->setRowClasses($rowc);
return id(new PHUIObjectBoxView())
->setHeaderText(pht('History'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($table);
}
-
- private function getStatus(HarbormasterBuild $build) {
- $status_view = new PHUIStatusListView();
-
- $item = new PHUIStatusItemView();
-
- if ($build->isPausing()) {
- $status_name = pht('Pausing');
- $icon = PHUIStatusItemView::ICON_RIGHT;
- $color = 'dark';
- } else {
- $status = $build->getBuildStatus();
- $status_name =
- HarbormasterBuildStatus::getBuildStatusName($status);
- $icon = HarbormasterBuildStatus::getBuildStatusIcon($status);
- $color = HarbormasterBuildStatus::getBuildStatusColor($status);
- }
-
- $item->setTarget($status_name);
- $item->setIcon($icon, $color);
- $status_view->addItem($item);
-
- return $status_view;
- }
-
private function buildMessages(array $messages) {
$viewer = $this->getRequest()->getUser();
if ($messages) {
$handles = id(new PhabricatorHandleQuery())
->setViewer($viewer)
->withPHIDs(mpull($messages, 'getAuthorPHID'))
->execute();
} else {
$handles = array();
}
$rows = array();
foreach ($messages as $message) {
$rows[] = array(
$message->getID(),
$handles[$message->getAuthorPHID()]->renderLink(),
$message->getType(),
$message->getIsConsumed() ? pht('Consumed') : null,
phabricator_datetime($message->getDateCreated(), $viewer),
);
}
$table = new AphrontTableView($rows);
$table->setNoDataString(pht('No messages for this build target.'));
$table->setHeaders(
array(
pht('ID'),
pht('From'),
pht('Type'),
pht('Consumed'),
pht('Received'),
));
$table->setColumnClasses(
array(
'',
'',
'wide',
'',
'date',
));
return $table;
}
private function buildProperties(array $properties) {
ksort($properties);
$rows = array();
foreach ($properties as $key => $value) {
$rows[] = array(
$key,
$value,
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
pht('Key'),
pht('Value'),
))
->setColumnClasses(
array(
'pri right',
'wide',
));
return $table;
}
}
diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php
index 954c710d90..a96ab13c9f 100644
--- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php
+++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php
@@ -1,613 +1,624 @@
<?php
final class HarbormasterBuild extends HarbormasterDAO
implements
PhabricatorApplicationTransactionInterface,
PhabricatorPolicyInterface,
PhabricatorConduitResultInterface,
PhabricatorDestructibleInterface {
protected $buildablePHID;
protected $buildPlanPHID;
protected $buildStatus;
protected $buildGeneration;
protected $buildParameters = array();
protected $initiatorPHID;
protected $planAutoKey;
private $buildable = self::ATTACHABLE;
private $buildPlan = self::ATTACHABLE;
private $buildTargets = self::ATTACHABLE;
private $unprocessedMessages = self::ATTACHABLE;
public static function initializeNewBuild(PhabricatorUser $actor) {
return id(new HarbormasterBuild())
->setBuildStatus(HarbormasterBuildStatus::STATUS_INACTIVE)
->setBuildGeneration(0);
}
public function delete() {
$this->openTransaction();
$this->deleteUnprocessedMessages();
$result = parent::delete();
$this->saveTransaction();
return $result;
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'buildParameters' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'buildStatus' => 'text32',
'buildGeneration' => 'uint32',
'planAutoKey' => 'text32?',
'initiatorPHID' => 'phid?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_buildable' => array(
'columns' => array('buildablePHID'),
),
'key_plan' => array(
'columns' => array('buildPlanPHID'),
),
'key_status' => array(
'columns' => array('buildStatus'),
),
'key_planautokey' => array(
'columns' => array('buildablePHID', 'planAutoKey'),
'unique' => true,
),
'key_initiator' => array(
'columns' => array('initiatorPHID'),
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
HarbormasterBuildPHIDType::TYPECONST);
}
public function attachBuildable(HarbormasterBuildable $buildable) {
$this->buildable = $buildable;
return $this;
}
public function getBuildable() {
return $this->assertAttached($this->buildable);
}
public function getName() {
if ($this->getBuildPlan()) {
return $this->getBuildPlan()->getName();
}
return pht('Build');
}
public function attachBuildPlan(
HarbormasterBuildPlan $build_plan = null) {
$this->buildPlan = $build_plan;
return $this;
}
public function getBuildPlan() {
return $this->assertAttached($this->buildPlan);
}
public function getBuildTargets() {
return $this->assertAttached($this->buildTargets);
}
public function attachBuildTargets(array $targets) {
$this->buildTargets = $targets;
return $this;
}
public function isBuilding() {
return $this->getBuildStatusObject()->isBuilding();
}
public function isAutobuild() {
return ($this->getPlanAutoKey() !== null);
}
public function retrieveVariablesFromBuild() {
$results = array(
'buildable.diff' => null,
'buildable.revision' => null,
'buildable.commit' => null,
'repository.callsign' => null,
'repository.phid' => null,
'repository.vcs' => null,
'repository.uri' => null,
'step.timestamp' => null,
'build.id' => null,
'initiator.phid' => null,
'buildable.phid' => null,
'buildable.object.phid' => null,
'buildable.container.phid' => null,
'build.phid' => null,
);
foreach ($this->getBuildParameters() as $key => $value) {
$results['build/'.$key] = $value;
}
$buildable = $this->getBuildable();
$object = $buildable->getBuildableObject();
$object_variables = $object->getBuildVariables();
$results = $object_variables + $results;
$results['step.timestamp'] = time();
$results['build.id'] = $this->getID();
$results['initiator.phid'] = $this->getInitiatorPHID();
$results['buildable.phid'] = $buildable->getPHID();
$results['buildable.object.phid'] = $object->getPHID();
$results['buildable.container.phid'] = $buildable->getContainerPHID();
$results['build.phid'] = $this->getPHID();
return $results;
}
public static function getAvailableBuildVariables() {
$objects = id(new PhutilClassMapQuery())
->setAncestorClass('HarbormasterBuildableInterface')
->execute();
$variables = array();
$variables[] = array(
'step.timestamp' => pht('The current UNIX timestamp.'),
'build.id' => pht('The ID of the current build.'),
'target.phid' => pht('The PHID of the current build target.'),
'initiator.phid' => pht(
'The PHID of the user or Object that initiated the build, '.
'if applicable.'),
'buildable.phid' => pht(
'The object PHID of the Harbormaster Buildable being built.'),
'buildable.object.phid' => pht(
'The object PHID of the object (usually a diff or commit) '.
'being built.'),
'buildable.container.phid' => pht(
'The object PHID of the container (usually a revision or repository) '.
'for the object being built.'),
'build.phid' => pht(
'The object PHID of the Harbormaster Build being built.'),
);
foreach ($objects as $object) {
$variables[] = $object->getAvailableBuildVariables();
}
$variables = array_mergev($variables);
return $variables;
}
public function isComplete() {
return $this->getBuildStatusObject()->isComplete();
}
public function isPaused() {
return $this->getBuildStatusObject()->isPaused();
}
public function isPassed() {
return $this->getBuildStatusObject()->isPassed();
}
public function isFailed() {
return $this->getBuildStatusObject()->isFailed();
}
public function getURI() {
$id = $this->getID();
return "/harbormaster/build/{$id}/";
}
+ public function getBuildPendingStatusObject() {
+
+ // NOTE: If a build has multiple unprocessed messages, we'll ignore
+ // messages that are obsoleted by a later or stronger message.
+ //
+ // For example, if a build has both "pause" and "abort" messages in queue,
+ // we just ignore the "pause" message and perform an "abort", since pausing
+ // first wouldn't affect the final state, so we can just skip it.
+ //
+ // Likewise, if a build has both "restart" and "abort" messages, the most
+ // recent message is controlling: we'll take whichever action a command
+ // was most recently issued for.
+
+ $is_restarting = false;
+ $is_aborting = false;
+ $is_pausing = false;
+ $is_resuming = false;
+
+ foreach ($this->getUnprocessedMessages() as $message_object) {
+ $message_type = $message_object->getType();
+ switch ($message_type) {
+ case HarbormasterBuildCommand::COMMAND_RESTART:
+ $is_restarting = true;
+ $is_aborting = false;
+ break;
+ case HarbormasterBuildCommand::COMMAND_ABORT:
+ $is_aborting = true;
+ $is_restarting = false;
+ break;
+ case HarbormasterBuildCommand::COMMAND_PAUSE:
+ $is_pausing = true;
+ $is_resuming = false;
+ break;
+ case HarbormasterBuildCommand::COMMAND_RESUME:
+ $is_resuming = true;
+ $is_pausing = false;
+ break;
+ }
+ }
+
+ $pending_status = null;
+ if ($is_restarting) {
+ $pending_status = HarbormasterBuildStatus::PENDING_RESTARTING;
+ } else if ($is_aborting) {
+ $pending_status = HarbormasterBuildStatus::PENDING_ABORTING;
+ } else if ($is_pausing) {
+ $pending_status = HarbormasterBuildStatus::PENDING_PAUSING;
+ } else if ($is_resuming) {
+ $pending_status = HarbormasterBuildStatus::PENDING_RESUMING;
+ }
+
+ if ($pending_status !== null) {
+ return HarbormasterBuildStatus::newBuildStatusObject($pending_status);
+ }
+
+ return $this->getBuildStatusObject();
+ }
+
protected function getBuildStatusObject() {
$status_key = $this->getBuildStatus();
return HarbormasterBuildStatus::newBuildStatusObject($status_key);
}
public function getObjectName() {
return pht('Build %d', $this->getID());
}
/* -( Build Messages )----------------------------------------------------- */
private function getUnprocessedMessages() {
return $this->assertAttached($this->unprocessedMessages);
}
public function attachUnprocessedMessages(array $messages) {
assert_instances_of($messages, 'HarbormasterBuildMessage');
$this->unprocessedMessages = $messages;
return $this;
}
public function canRestartBuild() {
try {
$this->assertCanRestartBuild();
return true;
} catch (HarbormasterRestartException $ex) {
return false;
}
}
public function assertCanRestartBuild() {
if ($this->isAutobuild()) {
throw new HarbormasterRestartException(
pht('Can Not Restart Autobuild'),
pht(
'This build can not be restarted because it is an automatic '.
'build.'));
}
$restartable = HarbormasterBuildPlanBehavior::BEHAVIOR_RESTARTABLE;
$plan = $this->getBuildPlan();
// See T13526. Users who can't see the "BuildPlan" can end up here with
// no object. This is highly questionable.
if (!$plan) {
throw new HarbormasterRestartException(
pht('No Build Plan Permission'),
pht(
'You can not restart this build because you do not have '.
'permission to access the build plan.'));
}
$option = HarbormasterBuildPlanBehavior::getBehavior($restartable)
->getPlanOption($plan);
$option_key = $option->getKey();
$never_restartable = HarbormasterBuildPlanBehavior::RESTARTABLE_NEVER;
$is_never = ($option_key === $never_restartable);
if ($is_never) {
throw new HarbormasterRestartException(
pht('Build Plan Prevents Restart'),
pht(
'This build can not be restarted because the build plan is '.
'configured to prevent the build from restarting.'));
}
$failed_restartable = HarbormasterBuildPlanBehavior::RESTARTABLE_IF_FAILED;
$is_failed = ($option_key === $failed_restartable);
if ($is_failed) {
if (!$this->isFailed()) {
throw new HarbormasterRestartException(
pht('Only Restartable if Failed'),
pht(
'This build can not be restarted because the build plan is '.
'configured to prevent the build from restarting unless it '.
'has failed, and it has not failed.'));
}
}
if ($this->isRestarting()) {
throw new HarbormasterRestartException(
pht('Already Restarting'),
pht(
'This build is already restarting. You can not reissue a restart '.
'command to a restarting build.'));
}
}
public function canPauseBuild() {
if ($this->isAutobuild()) {
return false;
}
return !$this->isComplete() &&
!$this->isPaused() &&
- !$this->isPausing();
+ !$this->isPausing() &&
+ !$this->isRestarting() &&
+ !$this->isAborting();
}
public function canAbortBuild() {
if ($this->isAutobuild()) {
return false;
}
- return !$this->isComplete();
+ return
+ !$this->isComplete() &&
+ !$this->isAborting();
}
public function canResumeBuild() {
if ($this->isAutobuild()) {
return false;
}
- return $this->isPaused() &&
- !$this->isResuming();
+ return
+ $this->isPaused() &&
+ !$this->isResuming() &&
+ !$this->isRestarting() &&
+ !$this->isAborting();
}
public function isPausing() {
- $is_pausing = false;
- foreach ($this->getUnprocessedMessages() as $message_object) {
- $message_type = $message_object->getType();
- switch ($message_type) {
- case HarbormasterBuildCommand::COMMAND_PAUSE:
- $is_pausing = true;
- break;
- case HarbormasterBuildCommand::COMMAND_RESUME:
- case HarbormasterBuildCommand::COMMAND_RESTART:
- $is_pausing = false;
- break;
- case HarbormasterBuildCommand::COMMAND_ABORT:
- $is_pausing = true;
- break;
- }
- }
-
- return $is_pausing;
+ return $this->getBuildPendingStatusObject()->isPausing();
}
public function isResuming() {
- $is_resuming = false;
- foreach ($this->getUnprocessedMessages() as $message_object) {
- $message_type = $message_object->getType();
- switch ($message_type) {
- case HarbormasterBuildCommand::COMMAND_RESTART:
- case HarbormasterBuildCommand::COMMAND_RESUME:
- $is_resuming = true;
- break;
- case HarbormasterBuildCommand::COMMAND_PAUSE:
- $is_resuming = false;
- break;
- case HarbormasterBuildCommand::COMMAND_ABORT:
- $is_resuming = false;
- break;
- }
- }
-
- return $is_resuming;
+ return $this->getBuildPendingStatusObject()->isResuming();
}
public function isRestarting() {
- $is_restarting = false;
- foreach ($this->getUnprocessedMessages() as $message_object) {
- $message_type = $message_object->getType();
- switch ($message_type) {
- case HarbormasterBuildCommand::COMMAND_RESTART:
- $is_restarting = true;
- break;
- }
- }
-
- return $is_restarting;
+ return $this->getBuildPendingStatusObject()->isRestarting();
}
public function isAborting() {
- $is_aborting = false;
- foreach ($this->getUnprocessedMessages() as $message_object) {
- $message_type = $message_object->getType();
- switch ($message_type) {
- case HarbormasterBuildCommand::COMMAND_ABORT:
- $is_aborting = true;
- break;
- }
- }
-
- return $is_aborting;
+ return $this->getBuildPendingStatusObject()->isAborting();
}
public function markUnprocessedMessagesAsProcessed() {
foreach ($this->getUnprocessedMessages() as $key => $message_object) {
$message_object
->setIsConsumed(1)
->save();
}
return $this;
}
public function deleteUnprocessedMessages() {
foreach ($this->getUnprocessedMessages() as $key => $message_object) {
$message_object->delete();
unset($this->unprocessedMessages[$key]);
}
return $this;
}
public function canIssueCommand(PhabricatorUser $viewer, $command) {
try {
$this->assertCanIssueCommand($viewer, $command);
return true;
} catch (Exception $ex) {
return false;
}
}
public function assertCanIssueCommand(PhabricatorUser $viewer, $command) {
$plan = $this->getBuildPlan();
// See T13526. Users without permission to access the build plan can
// currently end up here with no "BuildPlan" object.
if (!$plan) {
return false;
}
$need_edit = true;
switch ($command) {
case HarbormasterBuildCommand::COMMAND_RESTART:
case HarbormasterBuildCommand::COMMAND_PAUSE:
case HarbormasterBuildCommand::COMMAND_RESUME:
case HarbormasterBuildCommand::COMMAND_ABORT:
if ($plan->canRunWithoutEditCapability()) {
$need_edit = false;
}
break;
default:
throw new Exception(
pht(
'Invalid Harbormaster build command "%s".',
$command));
}
// Issuing these commands requires that you be able to edit the build, to
// prevent enemy engineers from sabotaging your builds. See T9614.
if ($need_edit) {
PhabricatorPolicyFilter::requireCapability(
$viewer,
$plan,
PhabricatorPolicyCapability::CAN_EDIT);
}
}
public function sendMessage(PhabricatorUser $viewer, $message_type) {
// TODO: This should not be an editor transaction, but there are plans to
// merge BuildCommand into BuildMessage which should moot this. As this
// exists today, it can race against BuildEngine.
// This is a bogus content source, but this whole flow should be obsolete
// soon.
$content_source = PhabricatorContentSource::newForSource(
PhabricatorConsoleContentSource::SOURCECONST);
$editor = id(new HarbormasterBuildTransactionEditor())
->setActor($viewer)
->setContentSource($content_source)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
$viewer_phid = $viewer->getPHID();
if (!$viewer_phid) {
$acting_phid = id(new PhabricatorHarbormasterApplication())->getPHID();
$editor->setActingAsPHID($acting_phid);
}
$xaction = id(new HarbormasterBuildTransaction())
->setTransactionType(HarbormasterBuildTransaction::TYPE_COMMAND)
->setNewValue($message_type);
$editor->applyTransactions($this, array($xaction));
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new HarbormasterBuildTransactionEditor();
}
public function getApplicationTransactionTemplate() {
return new HarbormasterBuildTransaction();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
return $this->getBuildable()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getBuildable()->hasAutomaticCapability(
$capability,
$viewer);
}
public function describeAutomaticCapability($capability) {
return pht('A build inherits policies from its buildable.');
}
/* -( PhabricatorConduitResultInterface )---------------------------------- */
public function getFieldSpecificationsForConduit() {
return array(
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('buildablePHID')
->setType('phid')
->setDescription(pht('PHID of the object this build is building.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('buildPlanPHID')
->setType('phid')
->setDescription(pht('PHID of the build plan being run.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('buildStatus')
->setType('map<string, wild>')
->setDescription(pht('The current status of this build.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('initiatorPHID')
->setType('phid')
->setDescription(pht('The person (or thing) that started this build.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('name')
->setType('string')
->setDescription(pht('The name of this build.')),
);
}
public function getFieldValuesForConduit() {
$status = $this->getBuildStatus();
return array(
'buildablePHID' => $this->getBuildablePHID(),
'buildPlanPHID' => $this->getBuildPlanPHID(),
'buildStatus' => array(
'value' => $status,
'name' => HarbormasterBuildStatus::getBuildStatusName($status),
'color.ansi' =>
HarbormasterBuildStatus::getBuildStatusANSIColor($status),
),
'initiatorPHID' => nonempty($this->getInitiatorPHID(), null),
'name' => $this->getName(),
);
}
public function getConduitSearchAttachments() {
return array(
id(new HarbormasterQueryBuildsSearchEngineAttachment())
->setAttachmentKey('querybuilds'),
);
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$viewer = $engine->getViewer();
$this->openTransaction();
$targets = id(new HarbormasterBuildTargetQuery())
->setViewer($viewer)
->withBuildPHIDs(array($this->getPHID()))
->execute();
foreach ($targets as $target) {
$engine->destroyObject($target);
}
$messages = id(new HarbormasterBuildMessageQuery())
->setViewer($viewer)
->withReceiverPHIDs(array($this->getPHID()))
->execute();
foreach ($messages as $message) {
$engine->destroyObject($message);
}
$this->delete();
$this->saveTransaction();
}
}

File Metadata

Mime Type
text/x-diff
Expires
Wed, Nov 26, 7:38 PM (19 h, 18 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
420126
Default Alt Text
(51 KB)

Event Timeline