Page MenuHomePhabricator

D7500.id16908.diff
No OneTemporary

D7500.id16908.diff

Index: src/__phutil_library_map__.php
===================================================================
--- src/__phutil_library_map__.php
+++ src/__phutil_library_map__.php
@@ -683,6 +683,9 @@
'HarbormasterRemarkupRule' => 'applications/harbormaster/remarkup/HarbormasterRemarkupRule.php',
'HarbormasterRunnerWorker' => 'applications/harbormaster/worker/HarbormasterRunnerWorker.php',
'HarbormasterScratchTable' => 'applications/harbormaster/storage/HarbormasterScratchTable.php',
+ 'HarbormasterStepAddController' => 'applications/harbormaster/controller/HarbormasterStepAddController.php',
+ 'HarbormasterStepDeleteController' => 'applications/harbormaster/controller/HarbormasterStepDeleteController.php',
+ 'HarbormasterStepEditController' => 'applications/harbormaster/controller/HarbormasterStepEditController.php',
'HeraldAction' => 'applications/herald/storage/HeraldAction.php',
'HeraldAdapter' => 'applications/herald/adapter/HeraldAdapter.php',
'HeraldApplyTranscript' => 'applications/herald/storage/transcript/HeraldApplyTranscript.php',
@@ -2914,6 +2917,9 @@
'HarbormasterRemarkupRule' => 'PhabricatorRemarkupRuleObject',
'HarbormasterRunnerWorker' => 'PhabricatorWorker',
'HarbormasterScratchTable' => 'HarbormasterDAO',
+ 'HarbormasterStepAddController' => 'HarbormasterController',
+ 'HarbormasterStepDeleteController' => 'HarbormasterController',
+ 'HarbormasterStepEditController' => 'HarbormasterController',
'HeraldAction' => 'HeraldDAO',
'HeraldApplyTranscript' => 'HeraldDAO',
'HeraldCapabilityManageGlobalRules' => 'PhabricatorPolicyCapability',
Index: src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php
===================================================================
--- src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php
+++ src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php
@@ -46,6 +46,11 @@
'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterBuildableEditController',
'apply/(?:(?P<id>\d+)/)?' => 'HarbormasterBuildableApplyController',
),
+ 'step/' => array(
+ 'add/(?:(?P<id>\d+)/)?' => 'HarbormasterStepAddController',
+ 'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterStepEditController',
+ 'delete/(?:(?P<id>\d+)/)?' => 'HarbormasterStepDeleteController',
+ ),
'plan/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?'
=> 'HarbormasterPlanListController',
Index: src/applications/harbormaster/controller/HarbormasterBuildableApplyController.php
===================================================================
--- src/applications/harbormaster/controller/HarbormasterBuildableApplyController.php
+++ src/applications/harbormaster/controller/HarbormasterBuildableApplyController.php
@@ -67,7 +67,6 @@
->setUser($viewer)
->setName('build-plan')
->setOptions($options));
- $dialog->addHiddenInput('confirmed', "true");
return id(new AphrontDialogResponse())->setDialog($dialog);
}
Index: src/applications/harbormaster/controller/HarbormasterPlanViewController.php
===================================================================
--- src/applications/harbormaster/controller/HarbormasterPlanViewController.php
+++ src/applications/harbormaster/controller/HarbormasterPlanViewController.php
@@ -55,10 +55,13 @@
id(new PhabricatorCrumbView())
->setName(pht("Plan %d", $id)));
+ $step_list = $this->buildStepList($plan);
+
return $this->buildApplicationPage(
array(
$crumbs,
$box,
+ $step_list,
$xaction_view,
),
array(
@@ -67,6 +70,56 @@
));
}
+ private function buildStepList(HarbormasterBuildPlan $plan) {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ $steps = id(new HarbormasterBuildStepQuery())
+ ->setViewer($viewer)
+ ->withBuildPlanPHIDs(array($plan->getPHID()))
+ ->execute();
+
+ $can_edit = $this->hasApplicationCapability(
+ HarbormasterCapabilityManagePlans::CAPABILITY);
+
+ $i = 1;
+ $step_list = id(new PHUIObjectItemListView())
+ ->setUser($viewer);
+ foreach ($steps as $step) {
+ $implementation = $step->getStepImplementation();
+ $item = id(new PHUIObjectItemView())
+ ->setObjectName("Step ".$i++)
+ ->setHeader($implementation->getName());
+
+ if (!$implementation->validateSettings()) {
+ $item
+ ->setBarColor('red')
+ ->addAttribute(pht('This step is not configured correctly.'));
+ } else {
+ $item->addAttribute($implementation->getDescription());
+ }
+
+ if ($can_edit) {
+ $edit_uri = $this->getApplicationURI("step/edit/".$step->getID()."/");
+ $item
+ ->setHref($edit_uri)
+ ->addAction(
+ id(new PHUIListItemView())
+ ->setIcon('delete')
+ ->addSigil('harbormaster-build-step-delete')
+ ->setWorkflow(true)
+ ->setRenderNameAsTooltip(true)
+ ->setName(pht("Delete"))
+ ->setHref(
+ $this->getApplicationURI("step/delete/".$step->getID()."/")));
+ }
+
+ $step_list->addItem($item);
+ }
+
+ return $step_list;
+ }
+
private function buildActionList(HarbormasterBuildPlan $plan) {
$request = $this->getRequest();
$viewer = $request->getUser();
@@ -90,6 +143,14 @@
$list->addAction(
id(new PhabricatorActionView())
+ ->setName(pht('Add Build Step'))
+ ->setHref($this->getApplicationURI("step/add/{$id}/"))
+ ->setWorkflow($can_edit)
+ ->setDisabled(!$can_edit)
+ ->setIcon('new'));
+
+ $list->addAction(
+ id(new PhabricatorActionView())
->setName(pht('Manually Execute Plan'))
->setHref($this->getApplicationURI("plan/execute/{$id}/"))
->setWorkflow(true)
Index: src/applications/harbormaster/controller/HarbormasterStepAddController.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/controller/HarbormasterStepAddController.php
@@ -0,0 +1,85 @@
+<?php
+
+final class HarbormasterStepAddController
+ extends HarbormasterController {
+
+ private $id;
+
+ public function willProcessRequest(array $data) {
+ $this->id = $data['id'];
+ }
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ $this->requireApplicationCapability(
+ HarbormasterCapabilityManagePlans::CAPABILITY);
+
+ $id = $this->id;
+
+ $plan = id(new HarbormasterBuildPlanQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($id))
+ ->executeOne();
+ if ($plan === null) {
+ throw new Exception("Build plan not found!");
+ }
+
+ $implementations = array(
+ 'SleepBuildStepImplementation');
+
+ if ($request->isDialogFormPost()) {
+ // TODO: Verify that the value is the name of a class
+ // that implements BuildStepImplementation.
+ $class = $request->getStr('step-type');
+ if (!in_array($class, $implementations)) {
+ return $this->createDialog($implementations);
+ }
+
+ $step = new HarbormasterBuildStep();
+ $step->setBuildPlanPHID($plan->getPHID());
+ $step->setClassName($class);
+ $step->setDetails(array());
+ $step->save();
+
+ $edit_uri = $this->getApplicationURI("step/edit/".$step->getID()."/");
+
+ return id(new AphrontRedirectResponse($edit_uri));
+ }
+
+ return $this->createDialog($implementations);
+ }
+
+ function createDialog(array $implementations) {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ $control = id(new AphrontFormRadioButtonControl())
+ ->setName('step-type');
+
+ foreach ($implementations as $implementation_name) {
+ $implementation = new $implementation_name();
+ $control
+ ->addButton(
+ $implementation_name,
+ $implementation->getName(),
+ $implementation->getGenericDescription());
+ }
+
+ $dialog = new AphrontDialogView();
+ $dialog->setTitle(pht('Add New Step'))
+ ->setUser($viewer)
+ ->addSubmitButton('Add')
+ ->addCancelButton('Cancel');
+ $dialog->appendChild(
+ phutil_tag(
+ 'p',
+ array(),
+ pht(
+ 'Select what type of build step you want to add: ')));
+ $dialog->appendChild($control);
+ return id(new AphrontDialogResponse())->setDialog($dialog);
+ }
+
+}
Index: src/applications/harbormaster/controller/HarbormasterStepDeleteController.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/controller/HarbormasterStepDeleteController.php
@@ -0,0 +1,50 @@
+<?php
+
+final class HarbormasterStepDeleteController
+ extends HarbormasterController {
+
+ private $id;
+
+ public function willProcessRequest(array $data) {
+ $this->id = $data['id'];
+ }
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ $this->requireApplicationCapability(
+ HarbormasterCapabilityManagePlans::CAPABILITY);
+
+ $id = $this->id;
+
+ $step = id(new HarbormasterBuildStepQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($id))
+ ->executeOne();
+ if ($step === null) {
+ throw new Exception("Build step not found!");
+ }
+
+ if ($request->isDialogFormPost()) {
+ $step->delete();
+
+ return id(new AphrontRedirectResponse());
+ }
+
+ $dialog = new AphrontDialogView();
+ $dialog->setTitle(pht('Really Delete Step?'))
+ ->setUser($viewer)
+ ->addSubmitButton('Delete')
+ ->addCancelButton('Cancel');
+ $dialog->appendChild(
+ phutil_tag(
+ 'p',
+ array(),
+ pht(
+ 'Are you sure you want to delete this '.
+ 'step? This can\'t be undone!')));
+ return id(new AphrontDialogResponse())->setDialog($dialog);
+ }
+
+}
Index: src/applications/harbormaster/controller/HarbormasterStepEditController.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/controller/HarbormasterStepEditController.php
@@ -0,0 +1,156 @@
+<?php
+
+final class HarbormasterStepEditController
+ extends HarbormasterController {
+
+ private $id;
+
+ public function willProcessRequest(array $data) {
+ $this->id = idx($data, 'id');
+ }
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ $this->requireApplicationCapability(
+ HarbormasterCapabilityManagePlans::CAPABILITY);
+
+ $step = id(new HarbormasterBuildStepQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($this->id))
+ ->executeOne();
+ if (!$step) {
+ return new Aphront404Response();
+ }
+
+ $plan = id(new HarbormasterBuildPlanQuery())
+ ->setViewer($viewer)
+ ->withPHIDs(array($step->getBuildPlanPHID()))
+ ->executeOne();
+ if (!$plan) {
+ return new Aphront404Response();
+ }
+
+ $implementation = $step->getStepImplementation();
+ $implementation->validateSettingDefinitions();
+ $settings = $implementation->getSettings();
+
+ $errors = array();
+ if ($request->isFormPost()) {
+ foreach ($implementation->getSettingDefinitions() as $name => $opt) {
+ $readable_name = $this->getReadableName($name, $opt);
+ $value = $this->getValueFromRequest($request, $name, $opt['type']);
+
+ // TODO: This won't catch any validation issues unless the field
+ // is missing completely. How should we check if the user is
+ // required to enter an integer?
+ if ($value === null) {
+ $errors[] = $readable_name.' is not valid.';
+ } else {
+ $step->setDetail($name, $value);
+ }
+ }
+
+ if (count($errors) === 0) {
+ $step->save();
+
+ return id(new AphrontRedirectResponse())
+ ->setURI($this->getApplicationURI('plan/'.$plan->getID().'/'));
+ }
+ }
+
+ $form = id(new AphrontFormView())
+ ->setUser($viewer);
+
+ // We need to render out all of the fields for the settings that
+ // the implementation has.
+ foreach ($implementation->getSettingDefinitions() as $name => $opt) {
+ if ($request->isFormPost()) {
+ $value = $this->getValueFromRequest($request, $name, $opt['type']);
+ } else {
+ $value = $settings[$name];
+ }
+
+ switch ($opt['type']) {
+ case BuildStepImplementation::SETTING_TYPE_STRING:
+ case BuildStepImplementation::SETTING_TYPE_INTEGER:
+ $control = id(new AphrontFormTextControl())
+ ->setLabel($this->getReadableName($name, $opt))
+ ->setName($name)
+ ->setValue($value);
+ break;
+ case BuildStepImplementation::SETTING_TYPE_BOOLEAN:
+ $control = id(new AphrontFormCheckboxControl())
+ ->setLabel($this->getReadableName($name, $opt))
+ ->setName($name)
+ ->setValue($value);
+ break;
+ default:
+ throw new Exception("Unable to render field with unknown type.");
+ }
+
+ if (isset($opt['description'])) {
+ $control->setCaption($opt['description']);
+ }
+
+ $form->appendChild($control);
+ }
+
+ $form->appendChild(
+ id(new AphrontFormSubmitControl())
+ ->setValue(pht('Save Step Configuration'))
+ ->addCancelButton(
+ $this->getApplicationURI('plan/'.$plan->getID().'/')));
+
+ $box = id(new PHUIObjectBoxView())
+ ->setHeaderText('Edit Step: '.$implementation->getName())
+ ->setValidationException(null)
+ ->setForm($form);
+
+ $crumbs = $this->buildApplicationCrumbs();
+ $id = $plan->getID();
+ $crumbs->addCrumb(
+ id(new PhabricatorCrumbView())
+ ->setName(pht("Plan %d", $id))
+ ->setHref($this->getApplicationURI("plan/{$id}/")));
+ $crumbs->addCrumb(
+ id(new PhabricatorCrumbView())
+ ->setName(pht('Edit Step')));
+
+ return $this->buildApplicationPage(
+ array(
+ $crumbs,
+ $box,
+ ),
+ array(
+ 'title' => $implementation->getName(),
+ 'device' => true,
+ ));
+ }
+
+ public function getReadableName($name, $opt) {
+ $readable_name = $name;
+ if (isset($opt['name'])) {
+ $readable_name = $opt['name'];
+ }
+ return $readable_name;
+ }
+
+ public function getValueFromRequest(AphrontRequest $request, $name, $type) {
+ switch ($type) {
+ case BuildStepImplementation::SETTING_TYPE_STRING:
+ return $request->getStr($name);
+ break;
+ case BuildStepImplementation::SETTING_TYPE_INTEGER:
+ return $request->getInt($name);
+ break;
+ case BuildStepImplementation::SETTING_TYPE_BOOLEAN:
+ return $request->getBool($name);
+ break;
+ default:
+ throw new Exception("Unsupported setting type '".$type."'.");
+ }
+ }
+
+}
Index: src/applications/harbormaster/daemon/PhabricatorHarbormasterBuildDaemon.php
===================================================================
--- src/applications/harbormaster/daemon/PhabricatorHarbormasterBuildDaemon.php
+++ src/applications/harbormaster/daemon/PhabricatorHarbormasterBuildDaemon.php
@@ -37,6 +37,10 @@
// Perform the build.
foreach ($steps as $step) {
$implementation = $step->getStepImplementation();
+ if (!$implementation->validateSettings()) {
+ $build->setBuildStatus(HarbormasterBuild::STATUS_FAILED);
+ break;
+ }
$implementation->execute($build);
if ($build->getBuildStatus() !== HarbormasterBuild::STATUS_BUILDING) {
break;
Index: src/applications/harbormaster/query/HarbormasterBuildStepQuery.php
===================================================================
--- src/applications/harbormaster/query/HarbormasterBuildStepQuery.php
+++ src/applications/harbormaster/query/HarbormasterBuildStepQuery.php
@@ -22,6 +22,14 @@
return $this;
}
+ public function getPagingColumn() {
+ return 'id';
+ }
+
+ public function getReversePaging() {
+ return true;
+ }
+
protected function loadPage() {
$table = new HarbormasterBuildStep();
$conn_r = $table->establishConnection('r');
Index: src/applications/harbormaster/step/BuildStepImplementation.php
===================================================================
--- src/applications/harbormaster/step/BuildStepImplementation.php
+++ src/applications/harbormaster/step/BuildStepImplementation.php
@@ -4,13 +4,24 @@
private $settings;
+ const SETTING_TYPE_STRING = 'string';
+ const SETTING_TYPE_INTEGER = 'integer';
+ const SETTING_TYPE_BOOLEAN = 'boolean';
+
/**
* The name of the implementation.
*/
abstract public function getName();
/**
- * The description of the implementation.
+ * The generic description of the implementation.
+ */
+ public function getGenericDescription() {
+ return '';
+ }
+
+ /**
+ * The description of the implementation, based on the current settings.
*/
public function getDescription() {
return '';
@@ -24,15 +35,23 @@
/**
* Gets the settings for this build step.
*/
- protected function getSettings() {
+ public function getSettings() {
return $this->settings;
}
/**
+ * Validate the current settings of this build step.
+ */
+ public function validate() {
+ return true;
+ }
+
+ /**
* Loads the settings for this build step implementation from the build step.
*/
public final function loadSettings(HarbormasterBuildStep $build_step) {
$this->settings = array();
+ $this->validateSettingDefinitions();
foreach ($this->getSettingDefinitions() as $name => $opt) {
$this->settings[$name] = $build_step->getDetail($name);
}
@@ -40,6 +59,19 @@
}
/**
+ * Validates that the setting definitions for this implementation are valid.
+ */
+ public final function validateSettingDefinitions() {
+ foreach ($this->getSettingDefinitions() as $name => $opt) {
+ if (!isset($opt['type'])) {
+ throw new Exception(
+ 'Setting definition \''.$name.
+ '\' is missing type requirement.');
+ }
+ }
+ }
+
+ /**
* Return an array of settings for this step implementation.
*/
public function getSettingDefinitions() {
Index: src/applications/harbormaster/step/SleepBuildStepImplementation.php
===================================================================
--- src/applications/harbormaster/step/SleepBuildStepImplementation.php
+++ src/applications/harbormaster/step/SleepBuildStepImplementation.php
@@ -6,19 +6,40 @@
return pht('Sleep');
}
- public function getDescription() {
+ public function getGenericDescription() {
return pht('Sleep for a specified number of seconds.');
}
+ public function getDescription() {
+ $settings = $this->getSettings();
+
+ return pht('Sleep for %s seconds.', $settings['seconds']);
+ }
+
public function execute(HarbormasterBuild $build) {
$settings = $this->getSettings();
sleep($settings['seconds']);
}
+ public function validateSettings() {
+ $settings = $this->getSettings();
+
+ if ($settings['seconds'] === null) {
+ return false;
+ }
+ if (!is_int($settings['seconds'])) {
+ return false;
+ }
+ return true;
+ }
+
public function getSettingDefinitions() {
return array(
- 'seconds' => array());
+ 'seconds' => array(
+ 'name' => 'Seconds',
+ 'description' => 'The number of seconds to sleep for.',
+ 'type' => BuildStepImplementation::SETTING_TYPE_INTEGER));
}
}
Index: src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php
===================================================================
--- src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php
+++ src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php
@@ -68,7 +68,14 @@
}
public function getPolicy($capability) {
- return $this->getBuildPlan()->getPolicy($capability);
+ // TODO: Can't access getBuildPlan here because it's not attached
+ // when querying for build steps. For daemons it's not an issue since
+ // policies don't apply, but when we try and list the steps in the
+ // UI, calling getBuildPlan will fail.
+ switch ($capability) {
+ case PhabricatorPolicyCapability::CAN_VIEW:
+ return PhabricatorPolicies::getMostOpenPolicy();
+ }
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {

File Metadata

Mime Type
text/plain
Expires
Tue, Oct 29, 10:04 PM (2 w, 5 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6753878
Default Alt Text
D7500.id16908.diff (20 KB)

Event Timeline