Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F25781842
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
26 KB
Subscribers
None
View Options
diff --git a/src/applications/harbormaster/controller/HarbormasterPlanRunController.php b/src/applications/harbormaster/controller/HarbormasterPlanRunController.php
index 0f5673087..980c7fad4 100644
--- a/src/applications/harbormaster/controller/HarbormasterPlanRunController.php
+++ b/src/applications/harbormaster/controller/HarbormasterPlanRunController.php
@@ -1,96 +1,104 @@
<?php
final class HarbormasterPlanRunController extends HarbormasterController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$this->requireApplicationCapability(
HarbormasterManagePlansCapability::CAPABILITY);
$plan_id = $request->getURIData('id');
// NOTE: At least for now, this only requires the "Can Manage Plans"
// capability, not the "Can Edit" capability. Possibly it should have
// a more stringent requirement, though.
$plan = id(new HarbormasterBuildPlanQuery())
->setViewer($viewer)
->withIDs(array($plan_id))
->executeOne();
if (!$plan) {
return new Aphront404Response();
}
+ $cancel_uri = $this->getApplicationURI("plan/{$plan_id}/");
+
+ if (!$plan->canRunManually()) {
+ return $this->newDialog()
+ ->setTitle(pht('Can Not Run Plan'))
+ ->appendParagraph(pht('This plan can not be run manually.'))
+ ->addCancelButton($cancel_uri);
+ }
+
$e_name = true;
$v_name = null;
$errors = array();
if ($request->isFormPost()) {
$buildable = HarbormasterBuildable::initializeNewBuildable($viewer)
->setIsManualBuildable(true);
$v_name = $request->getStr('buildablePHID');
if ($v_name) {
$object = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withNames(array($v_name))
->executeOne();
if ($object instanceof HarbormasterBuildableInterface) {
$buildable
->setBuildablePHID($object->getHarbormasterBuildablePHID())
->setContainerPHID($object->getHarbormasterContainerPHID());
} else {
$e_name = pht('Invalid');
$errors[] = pht('Enter the name of a revision or commit.');
}
} else {
$e_name = pht('Required');
$errors[] = pht('You must choose a revision or commit to build.');
}
if (!$errors) {
$buildable->save();
$buildable->applyPlan($plan);
$buildable_uri = '/B'.$buildable->getID();
return id(new AphrontRedirectResponse())->setURI($buildable_uri);
}
}
if ($errors) {
$errors = id(new PHUIInfoView())->setErrors($errors);
}
$title = pht('Run Build Plan Manually');
- $cancel_uri = $this->getApplicationURI("plan/{$plan_id}/");
$save_button = pht('Run Plan Manually');
$form = id(new PHUIFormLayoutView())
->setUser($viewer)
->appendRemarkupInstructions(
pht(
"Enter the name of a commit or revision to run this plan on (for ".
"example, `rX123456` or `D123`).\n\n".
"For more detailed output, you can also run manual builds from ".
"the command line:\n\n".
" phabricator/ $ ./bin/harbormaster build <object> --plan %s",
$plan->getID()))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Buildable Name'))
->setName('buildablePHID')
->setError($e_name)
->setValue($v_name));
return $this->newDialog()
->setWidth(AphrontDialogView::WIDTH_FULL)
->setTitle($title)
->appendChild($form)
->addCancelButton($cancel_uri)
->addSubmitButton($save_button);
}
}
diff --git a/src/applications/harbormaster/controller/HarbormasterPlanViewController.php b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php
index da35e389a..95db4c52c 100644
--- a/src/applications/harbormaster/controller/HarbormasterPlanViewController.php
+++ b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php
@@ -1,492 +1,494 @@
<?php
final class HarbormasterPlanViewController extends HarbormasterPlanController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getviewer();
$id = $request->getURIData('id');
$plan = id(new HarbormasterBuildPlanQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$plan) {
return new Aphront404Response();
}
$timeline = $this->buildTransactionTimeline(
$plan,
new HarbormasterBuildPlanTransactionQuery());
$timeline->setShouldTerminate(true);
$title = $plan->getName();
$header = id(new PHUIHeaderView())
->setHeader($plan->getName())
->setUser($viewer)
->setPolicyObject($plan);
$box = id(new PHUIObjectBoxView())
->setHeader($header);
$actions = $this->buildActionList($plan);
$this->buildPropertyLists($box, $plan, $actions);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Plan %d', $id));
list($step_list, $has_any_conflicts, $would_deadlock) =
$this->buildStepList($plan);
if ($would_deadlock) {
$box->setFormErrors(
array(
pht(
'This build plan will deadlock when executed, due to '.
'circular dependencies present in the build plan. '.
'Examine the step list and resolve the deadlock.'),
));
} else if ($has_any_conflicts) {
// A deadlocking build will also cause all the artifacts to be
// invalid, so we just skip showing this message if that's the
// case.
$box->setFormErrors(
array(
pht(
'This build plan has conflicts in one or more build steps. '.
'Examine the step list and resolve the listed errors.'),
));
}
return $this->buildApplicationPage(
array(
$crumbs,
$box,
$step_list,
$timeline,
),
array(
'title' => $title,
));
}
private function buildStepList(HarbormasterBuildPlan $plan) {
$viewer = $this->getViewer();
$run_order = HarbormasterBuildGraph::determineDependencyExecution($plan);
$steps = id(new HarbormasterBuildStepQuery())
->setViewer($viewer)
->withBuildPlanPHIDs(array($plan->getPHID()))
->execute();
$steps = mpull($steps, null, 'getPHID');
$has_manage = $this->hasApplicationCapability(
HarbormasterManagePlansCapability::CAPABILITY);
$has_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$plan,
PhabricatorPolicyCapability::CAN_EDIT);
$can_edit = ($has_manage && $has_edit);
$step_list = id(new PHUIObjectItemListView())
->setUser($viewer)
->setNoDataString(
pht('This build plan does not have any build steps yet.'));
$i = 1;
$last_depth = 0;
$has_any_conflicts = false;
$is_deadlocking = false;
foreach ($run_order as $run_ref) {
$step = $steps[$run_ref['node']->getPHID()];
$depth = $run_ref['depth'] + 1;
if ($last_depth !== $depth) {
$last_depth = $depth;
$i = 1;
} else {
$i++;
}
$implementation = null;
try {
$implementation = $step->getStepImplementation();
} catch (Exception $ex) {
// We can't initialize the implementation. This might be because
// it's been renamed or no longer exists.
$item = id(new PHUIObjectItemView())
->setObjectName(pht('Step %d.%d', $depth, $i))
->setHeader(pht('Unknown Implementation'))
->setStatusIcon('fa-warning red')
->addAttribute(pht(
'This step has an invalid implementation (%s).',
$step->getClassName()))
->addAction(
id(new PHUIListItemView())
->setIcon('fa-times')
->addSigil('harbormaster-build-step-delete')
->setWorkflow(true)
->setRenderNameAsTooltip(true)
->setName(pht('Delete'))
->setHref(
$this->getApplicationURI('step/delete/'.$step->getID().'/')));
$step_list->addItem($item);
continue;
}
$item = id(new PHUIObjectItemView())
->setObjectName(pht('Step %d.%d', $depth, $i))
->setHeader($step->getName());
$item->addAttribute($implementation->getDescription());
$step_id = $step->getID();
$edit_uri = $this->getApplicationURI("step/edit/{$step_id}/");
$delete_uri = $this->getApplicationURI("step/delete/{$step_id}/");
if ($can_edit) {
$item->setHref($edit_uri);
}
$item
->setHref($edit_uri)
->addAction(
id(new PHUIListItemView())
->setIcon('fa-times')
->addSigil('harbormaster-build-step-delete')
->setWorkflow(true)
->setDisabled(!$can_edit)
->setHref(
$this->getApplicationURI('step/delete/'.$step->getID().'/')));
$depends = $step->getStepImplementation()->getDependencies($step);
$inputs = $step->getStepImplementation()->getArtifactInputs();
$outputs = $step->getStepImplementation()->getArtifactOutputs();
$has_conflicts = false;
if ($depends || $inputs || $outputs) {
$available_artifacts =
HarbormasterBuildStepImplementation::getAvailableArtifacts(
$plan,
$step,
null);
$available_artifacts = ipull($available_artifacts, 'type');
list($depends_ui, $has_conflicts) = $this->buildDependsOnList(
$depends,
pht('Depends On'),
$steps);
list($inputs_ui, $has_conflicts) = $this->buildArtifactList(
$inputs,
'in',
pht('Input Artifacts'),
$available_artifacts);
list($outputs_ui) = $this->buildArtifactList(
$outputs,
'out',
pht('Output Artifacts'),
array());
$item->appendChild(
phutil_tag(
'div',
array(
'class' => 'harbormaster-artifact-io',
),
array(
$depends_ui,
$inputs_ui,
$outputs_ui,
)));
}
if ($has_conflicts) {
$has_any_conflicts = true;
$item->setStatusIcon('fa-warning red');
}
if ($run_ref['cycle']) {
$is_deadlocking = true;
}
if ($is_deadlocking) {
$item->setStatusIcon('fa-warning red');
}
$step_list->addItem($item);
}
$step_list->setFlush(true);
$plan_id = $plan->getID();
$header = id(new PHUIHeaderView())
->setHeader(pht('Build Steps'))
->addActionLink(
id(new PHUIButtonView())
->setText(pht('Add Build Step'))
->setHref($this->getApplicationURI("step/add/{$plan_id}/"))
->setTag('a')
->setIcon(
id(new PHUIIconView())
->setIconFont('fa-plus'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$step_box = id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($step_list);
return array($step_box, $has_any_conflicts, $is_deadlocking);
}
private function buildActionList(HarbormasterBuildPlan $plan) {
$viewer = $this->getViewer();
$id = $plan->getID();
$list = id(new PhabricatorActionListView())
->setUser($viewer)
->setObject($plan)
->setObjectURI($this->getApplicationURI("plan/{$id}/"));
$has_manage = $this->hasApplicationCapability(
HarbormasterManagePlansCapability::CAPABILITY);
$has_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$plan,
PhabricatorPolicyCapability::CAN_EDIT);
$can_edit = ($has_manage && $has_edit);
$list->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Plan'))
->setHref($this->getApplicationURI("plan/edit/{$id}/"))
->setWorkflow(!$can_edit)
->setDisabled(!$can_edit)
->setIcon('fa-pencil'));
if ($plan->isDisabled()) {
$list->addAction(
id(new PhabricatorActionView())
->setName(pht('Enable Plan'))
->setHref($this->getApplicationURI("plan/disable/{$id}/"))
->setWorkflow(true)
->setDisabled(!$can_edit)
->setIcon('fa-check'));
} else {
$list->addAction(
id(new PhabricatorActionView())
->setName(pht('Disable Plan'))
->setHref($this->getApplicationURI("plan/disable/{$id}/"))
->setWorkflow(true)
->setDisabled(!$can_edit)
->setIcon('fa-ban'));
}
+ $can_run = ($has_manage && $plan->canRunManually());
+
$list->addAction(
id(new PhabricatorActionView())
->setName(pht('Run Plan Manually'))
->setHref($this->getApplicationURI("plan/run/{$id}/"))
->setWorkflow(true)
- ->setDisabled(!$has_manage)
+ ->setDisabled(!$can_run)
->setIcon('fa-play-circle'));
return $list;
}
private function buildPropertyLists(
PHUIObjectBoxView $box,
HarbormasterBuildPlan $plan,
PhabricatorActionListView $actions) {
$request = $this->getRequest();
$viewer = $request->getUser();
$properties = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($plan)
->setActionList($actions);
$box->addPropertyList($properties);
$properties->addProperty(
pht('Created'),
phabricator_datetime($plan->getDateCreated(), $viewer));
}
private function buildArtifactList(
array $artifacts,
$kind,
$name,
array $available_artifacts) {
$has_conflicts = false;
if (!$artifacts) {
return array(null, $has_conflicts);
}
$this->requireResource('harbormaster-css');
$header = phutil_tag(
'div',
array(
'class' => 'harbormaster-artifact-summary-header',
),
$name);
$is_input = ($kind == 'in');
$list = new PHUIStatusListView();
foreach ($artifacts as $artifact) {
$error = null;
$key = idx($artifact, 'key');
if (!strlen($key)) {
$bound = phutil_tag('em', array(), pht('(null)'));
if ($is_input) {
// This is an unbound input. For now, all inputs are always required.
$icon = PHUIStatusItemView::ICON_WARNING;
$color = 'red';
$icon_label = pht('Required Input');
$has_conflicts = true;
$error = pht('This input is required, but not configured.');
} else {
// This is an unnamed output. Outputs do not necessarily need to be
// named.
$icon = PHUIStatusItemView::ICON_OPEN;
$color = 'bluegrey';
$icon_label = pht('Unused Output');
}
} else {
$bound = phutil_tag('strong', array(), $key);
if ($is_input) {
if (isset($available_artifacts[$key])) {
if ($available_artifacts[$key] == idx($artifact, 'type')) {
$icon = PHUIStatusItemView::ICON_ACCEPT;
$color = 'green';
$icon_label = pht('Valid Input');
} else {
$icon = PHUIStatusItemView::ICON_WARNING;
$color = 'red';
$icon_label = pht('Bad Input Type');
$has_conflicts = true;
$error = pht(
'This input is bound to the wrong artifact type. It is bound '.
'to a "%s" artifact, but should be bound to a "%s" artifact.',
$available_artifacts[$key],
idx($artifact, 'type'));
}
} else {
$icon = PHUIStatusItemView::ICON_QUESTION;
$color = 'red';
$icon_label = pht('Unknown Input');
$has_conflicts = true;
$error = pht(
'This input is bound to an artifact ("%s") which does not exist '.
'at this stage in the build process.',
$key);
}
} else {
$icon = PHUIStatusItemView::ICON_DOWN;
$color = 'green';
$icon_label = pht('Valid Output');
}
}
if ($error) {
$note = array(
phutil_tag('strong', array(), pht('ERROR:')),
' ',
$error,
);
} else {
$note = $bound;
}
$list->addItem(
id(new PHUIStatusItemView())
->setIcon($icon, $color, $icon_label)
->setTarget($artifact['name'])
->setNote($note));
}
$ui = array(
$header,
$list,
);
return array($ui, $has_conflicts);
}
private function buildDependsOnList(
array $step_phids,
$name,
array $steps) {
$has_conflicts = false;
if (count($step_phids) === 0) {
return null;
}
$this->requireResource('harbormaster-css');
$steps = mpull($steps, null, 'getPHID');
$header = phutil_tag(
'div',
array(
'class' => 'harbormaster-artifact-summary-header',
),
$name);
$list = new PHUIStatusListView();
foreach ($step_phids as $step_phid) {
$error = null;
if (idx($steps, $step_phid) === null) {
$icon = PHUIStatusItemView::ICON_WARNING;
$color = 'red';
$icon_label = pht('Missing Dependency');
$has_conflicts = true;
$error = pht(
"This dependency specifies a build step which doesn't exist.");
} else {
$bound = phutil_tag(
'strong',
array(),
idx($steps, $step_phid)->getName());
$icon = PHUIStatusItemView::ICON_ACCEPT;
$color = 'green';
$icon_label = pht('Valid Input');
}
if ($error) {
$note = array(
phutil_tag('strong', array(), pht('ERROR:')),
' ',
$error,
);
} else {
$note = $bound;
}
$list->addItem(
id(new PHUIStatusItemView())
->setIcon($icon, $color, $icon_label)
->setTarget(pht('Build Step'))
->setNote($note));
}
$ui = array(
$header,
$list,
);
return array($ui, $has_conflicts);
}
}
diff --git a/src/applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php b/src/applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php
index 86b8596fe..fc0f67063 100644
--- a/src/applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php
+++ b/src/applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php
@@ -1,94 +1,99 @@
<?php
final class HarbormasterManagementBuildWorkflow
extends HarbormasterManagementWorkflow {
protected function didConstruct() {
$this
->setName('build')
->setExamples('**build** [__options__] __buildable__ --plan __id__')
->setSynopsis(pht('Run plan __id__ on __buildable__.'))
->setArguments(
array(
array(
'name' => 'plan',
'param' => 'id',
'help' => pht('ID of build plan to run.'),
),
array(
'name' => 'buildable',
'wildcard' => true,
),
));
}
public function execute(PhutilArgumentParser $args) {
$viewer = $this->getViewer();
$names = $args->getArg('buildable');
if (count($names) != 1) {
throw new PhutilArgumentUsageException(
pht('Specify exactly one buildable object, by object name.'));
}
$name = head($names);
$buildable = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withNames($names)
->executeOne();
if (!$buildable) {
throw new PhutilArgumentUsageException(
pht('No such buildable "%s"!', $name));
}
if (!($buildable instanceof HarbormasterBuildableInterface)) {
throw new PhutilArgumentUsageException(
pht('Object "%s" is not a buildable!', $name));
}
$plan_id = $args->getArg('plan');
if (!$plan_id) {
throw new PhutilArgumentUsageException(
pht(
'Use %s to specify a build plan to run.',
'--plan'));
}
$plan = id(new HarbormasterBuildPlanQuery())
->setViewer($viewer)
->withIDs(array($plan_id))
->executeOne();
if (!$plan) {
throw new PhutilArgumentUsageException(
pht('Build plan "%s" does not exist.', $plan_id));
}
+ if (!$plan->canRunManually()) {
+ throw new PhutilArgumentUsageException(
+ pht('This build plan can not be run manually.'));
+ }
+
$console = PhutilConsole::getConsole();
$buildable = HarbormasterBuildable::initializeNewBuildable($viewer)
->setIsManualBuildable(true)
->setBuildablePHID($buildable->getHarbormasterBuildablePHID())
->setContainerPHID($buildable->getHarbormasterContainerPHID())
->save();
$console->writeOut(
"%s\n",
pht(
'Applying plan %s to new buildable %s...',
$plan->getID(),
'B'.$buildable->getID()));
$console->writeOut(
"\n %s\n\n",
PhabricatorEnv::getProductionURI('/B'.$buildable->getID()));
PhabricatorWorker::setRunAllTasksInProcess(true);
$buildable->applyPlan($plan);
$console->writeOut("%s\n", pht('Done.'));
return 0;
}
}
diff --git a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php
index 7b15b0034..cf3d9ee7d 100644
--- a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php
+++ b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php
@@ -1,183 +1,192 @@
<?php
/**
* @task autoplan Autoplans
*/
final class HarbormasterBuildPlan extends HarbormasterDAO
implements
PhabricatorApplicationTransactionInterface,
PhabricatorPolicyInterface,
PhabricatorSubscribableInterface {
protected $name;
protected $planStatus;
protected $planAutoKey;
const STATUS_ACTIVE = 'active';
const STATUS_DISABLED = 'disabled';
private $buildSteps = self::ATTACHABLE;
public static function initializeNewBuildPlan(PhabricatorUser $actor) {
return id(new HarbormasterBuildPlan())
->setName('')
->setPlanStatus(self::STATUS_ACTIVE)
->attachBuildSteps(array());
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'sort128',
'planStatus' => 'text32',
'planAutoKey' => 'text32?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_status' => array(
'columns' => array('planStatus'),
),
'key_name' => array(
'columns' => array('name'),
),
'key_planautokey' => array(
'columns' => array('planAutoKey'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
HarbormasterBuildPlanPHIDType::TYPECONST);
}
public function attachBuildSteps(array $steps) {
assert_instances_of($steps, 'HarbormasterBuildStep');
$this->buildSteps = $steps;
return $this;
}
public function getBuildSteps() {
return $this->assertAttached($this->buildSteps);
}
public function isDisabled() {
return ($this->getPlanStatus() == self::STATUS_DISABLED);
}
/* -( Autoplans )---------------------------------------------------------- */
public function isAutoplan() {
return ($this->getPlanAutoKey() !== null);
}
public function getAutoplan() {
if (!$this->isAutoplan()) {
return null;
}
return HarbormasterBuildAutoplan::getAutoplan($this->getPlanAutoKey());
}
+ public function canRunManually() {
+ if ($this->isAutoplan()) {
+ return false;
+ }
+
+ return true;
+ }
+
+
public function getName() {
$autoplan = $this->getAutoplan();
if ($autoplan) {
return $autoplan->getAutoplanName();
}
return parent::getName();
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */
public function isAutomaticallySubscribed($phid) {
return false;
}
public function shouldShowSubscribersProperty() {
return true;
}
public function shouldAllowSubscription($phid) {
return true;
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new HarbormasterBuildPlanEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new HarbormasterBuildPlanTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return PhabricatorPolicies::getMostOpenPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
// NOTE: In practice, this policy is always limited by the "Mangage
// Build Plans" policy.
if ($this->isAutoplan()) {
return PhabricatorPolicies::POLICY_NOONE;
}
return PhabricatorPolicies::getMostOpenPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
public function describeAutomaticCapability($capability) {
$messages = array();
switch ($capability) {
case PhabricatorPolicyCapability::CAN_EDIT:
if ($this->isAutoplan()) {
$messages[] = pht(
'This is an autoplan (a builtin plan provided by an application) '.
'so it can not be edited.');
}
break;
}
return $messages;
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Jul 12, 10:26 AM (1 d, 11 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
45/39/4441c3c4248d3178b44ac38fb9c7
Attached To
rPHAB Phabricator
Event Timeline
Log In to Comment