Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14007015
D7500.id16911.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
21 KB
Referenced Files
None
Subscribers
None
D7500.id16911.diff
View Options
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,82 @@
+<?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 = BuildStepImplementation::getImplementations();
+
+ if ($request->isDialogFormPost()) {
+ $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,32 @@
private $settings;
+ const SETTING_TYPE_STRING = 'string';
+ const SETTING_TYPE_INTEGER = 'integer';
+ const SETTING_TYPE_BOOLEAN = 'boolean';
+
+ public static function getImplementations() {
+ $symbols = id(new PhutilSymbolLoader())
+ ->setAncestorClass("BuildStepImplementation")
+ ->setConcreteOnly(true)
+ ->selectAndLoadSymbols();
+ return ipull($symbols, 'name');
+ }
+
/**
* 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 +43,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 +67,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
@@ -46,9 +46,16 @@
throw new Exception("No implementation set for the given step.");
}
- // TODO: We should look up the class in phutil's system to ensure
- // that it actually extends BuildStepImplementation.
+ static $implementations = null;
+ if ($implementations === null) {
+ $implementations = BuildStepImplementation::getImplementations();
+ }
+
$class = $this->className;
+ if (!in_array($class, $implementations)) {
+ throw new Exception(
+ "Class name '".$class."' does not extend BuildStepImplementation.");
+ }
$implementation = new $class();
if ($implementation === null) {
throw new Exception("Unable to create implementation.");
@@ -68,7 +75,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
Details
Attached
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
6753877
Default Alt Text
D7500.id16911.diff (21 KB)
Attached To
Mode
D7500: Implement interface for adding, editing and deleting build steps on plans.
Attached
Detach File
Event Timeline
Log In to Comment