mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-26 08:42:41 +01:00
Use ApplicationTransactions and CustomField to implement build steps
Summary: Ref T1049. Fixes T4602. Moves all the funky field stuff to CustomField. Uses ApplicationTransactions to apply and record edits. This makes "artifact" fields a little less nice (but still perfectly usable). With D8599, I think they're reasonable overall. We can improve this in the future. All other field types are better (e.g., fixes weird bugs with "bool", fixes lots of weird behavior around required fields), and this gives us access to many new field types. Test Plan: Made a bunch of step edits. Here's an example: {F133694} Note that: - "Required" fields work correctly. - the transaction record is shown at the bottom of the page. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T4602, T1049 Differential Revision: https://secure.phabricator.com/D8600
This commit is contained in:
parent
72337dedaf
commit
a246c85c6b
24 changed files with 273 additions and 317 deletions
21
resources/sql/autopatches/20140321.harbor.1.bxaction.sql
Normal file
21
resources/sql/autopatches/20140321.harbor.1.bxaction.sql
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildsteptransaction (
|
||||||
|
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||||
|
authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||||
|
objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||||
|
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||||
|
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||||
|
commentPHID VARCHAR(64) COLLATE utf8_bin,
|
||||||
|
commentVersion INT UNSIGNED NOT NULL,
|
||||||
|
transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin,
|
||||||
|
oldValue LONGTEXT NOT NULL COLLATE utf8_bin,
|
||||||
|
newValue LONGTEXT NOT NULL COLLATE utf8_bin,
|
||||||
|
contentSource LONGTEXT NOT NULL COLLATE utf8_bin,
|
||||||
|
metadata LONGTEXT NOT NULL COLLATE utf8_bin,
|
||||||
|
dateCreated INT UNSIGNED NOT NULL,
|
||||||
|
dateModified INT UNSIGNED NOT NULL,
|
||||||
|
|
||||||
|
UNIQUE KEY `key_phid` (phid),
|
||||||
|
KEY `key_object` (objectPHID)
|
||||||
|
|
||||||
|
) ENGINE=InnoDB, COLLATE utf8_general_ci;
|
|
@ -712,7 +712,12 @@ phutil_register_library_map(array(
|
||||||
'HarbormasterBuildPlanTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php',
|
'HarbormasterBuildPlanTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php',
|
||||||
'HarbormasterBuildQuery' => 'applications/harbormaster/query/HarbormasterBuildQuery.php',
|
'HarbormasterBuildQuery' => 'applications/harbormaster/query/HarbormasterBuildQuery.php',
|
||||||
'HarbormasterBuildStep' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStep.php',
|
'HarbormasterBuildStep' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStep.php',
|
||||||
|
'HarbormasterBuildStepCoreCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php',
|
||||||
|
'HarbormasterBuildStepCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCustomField.php',
|
||||||
|
'HarbormasterBuildStepEditor' => 'applications/harbormaster/editor/HarbormasterBuildStepEditor.php',
|
||||||
'HarbormasterBuildStepQuery' => 'applications/harbormaster/query/HarbormasterBuildStepQuery.php',
|
'HarbormasterBuildStepQuery' => 'applications/harbormaster/query/HarbormasterBuildStepQuery.php',
|
||||||
|
'HarbormasterBuildStepTransaction' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStepTransaction.php',
|
||||||
|
'HarbormasterBuildStepTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildStepTransactionQuery.php',
|
||||||
'HarbormasterBuildTarget' => 'applications/harbormaster/storage/build/HarbormasterBuildTarget.php',
|
'HarbormasterBuildTarget' => 'applications/harbormaster/storage/build/HarbormasterBuildTarget.php',
|
||||||
'HarbormasterBuildTargetQuery' => 'applications/harbormaster/query/HarbormasterBuildTargetQuery.php',
|
'HarbormasterBuildTargetQuery' => 'applications/harbormaster/query/HarbormasterBuildTargetQuery.php',
|
||||||
'HarbormasterBuildViewController' => 'applications/harbormaster/controller/HarbormasterBuildViewController.php',
|
'HarbormasterBuildViewController' => 'applications/harbormaster/controller/HarbormasterBuildViewController.php',
|
||||||
|
@ -3310,8 +3315,18 @@ phutil_register_library_map(array(
|
||||||
array(
|
array(
|
||||||
0 => 'HarbormasterDAO',
|
0 => 'HarbormasterDAO',
|
||||||
1 => 'PhabricatorPolicyInterface',
|
1 => 'PhabricatorPolicyInterface',
|
||||||
|
2 => 'PhabricatorCustomFieldInterface',
|
||||||
),
|
),
|
||||||
|
'HarbormasterBuildStepCoreCustomField' =>
|
||||||
|
array(
|
||||||
|
0 => 'HarbormasterBuildStepCustomField',
|
||||||
|
1 => 'PhabricatorStandardCustomFieldInterface',
|
||||||
|
),
|
||||||
|
'HarbormasterBuildStepCustomField' => 'PhabricatorCustomField',
|
||||||
|
'HarbormasterBuildStepEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||||
'HarbormasterBuildStepQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
'HarbormasterBuildStepQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
|
'HarbormasterBuildStepTransaction' => 'PhabricatorApplicationTransaction',
|
||||||
|
'HarbormasterBuildStepTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||||
'HarbormasterBuildTarget' =>
|
'HarbormasterBuildTarget' =>
|
||||||
array(
|
array(
|
||||||
0 => 'HarbormasterDAO',
|
0 => 'HarbormasterDAO',
|
||||||
|
|
|
@ -27,85 +27,39 @@ final class HarbormasterStepEditController
|
||||||
$plan = $step->getBuildPlan();
|
$plan = $step->getBuildPlan();
|
||||||
|
|
||||||
$implementation = $step->getStepImplementation();
|
$implementation = $step->getStepImplementation();
|
||||||
$implementation->validateSettingDefinitions();
|
|
||||||
$settings = $implementation->getSettings();
|
$field_list = PhabricatorCustomField::getObjectFields(
|
||||||
|
$step,
|
||||||
|
PhabricatorCustomField::ROLE_EDIT);
|
||||||
|
$field_list
|
||||||
|
->setViewer($viewer)
|
||||||
|
->readFieldsFromStorage($step);
|
||||||
|
|
||||||
$errors = array();
|
$errors = array();
|
||||||
|
$validation_exception = null;
|
||||||
if ($request->isFormPost()) {
|
if ($request->isFormPost()) {
|
||||||
foreach ($implementation->getSettingDefinitions() as $name => $opt) {
|
$xactions = $field_list->buildFieldTransactionsFromRequest(
|
||||||
$readable_name = $this->getReadableName($name, $opt);
|
new HarbormasterBuildStepTransaction(),
|
||||||
$value = $this->getValueFromRequest($request, $name, $opt['type']);
|
$request);
|
||||||
|
|
||||||
// TODO: This won't catch any validation issues unless the field
|
$editor = id(new HarbormasterBuildStepEditor())
|
||||||
// is missing completely. How should we check if the user is
|
->setActor($viewer)
|
||||||
// required to enter an integer?
|
->setContinueOnNoEffect(true)
|
||||||
if ($value === null) {
|
->setContentSourceFromRequest($request);
|
||||||
$errors[] = $readable_name.' is not valid.';
|
|
||||||
} else {
|
|
||||||
$step->setDetail($name, $value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$errors) {
|
try {
|
||||||
$step->save();
|
$editor->applyTransactions($step, $xactions);
|
||||||
return id(new AphrontRedirectResponse())
|
return id(new AphrontRedirectResponse())
|
||||||
->setURI($this->getApplicationURI('plan/'.$plan->getID().'/'));
|
->setURI($this->getApplicationURI('plan/'.$plan->getID().'/'));
|
||||||
|
} catch (PhabricatorApplicationTransactionValidationException $ex) {
|
||||||
|
$validation_exception = $ex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$form = id(new AphrontFormView())
|
$form = id(new AphrontFormView())
|
||||||
->setUser($viewer);
|
->setUser($viewer);
|
||||||
|
|
||||||
// We need to render out all of the fields for the settings that
|
$field_list->appendFieldsToForm($form);
|
||||||
// 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;
|
|
||||||
case BuildStepImplementation::SETTING_TYPE_ARTIFACT:
|
|
||||||
$filter = $opt['artifact_type'];
|
|
||||||
$available_artifacts =
|
|
||||||
BuildStepImplementation::loadAvailableArtifacts(
|
|
||||||
$plan,
|
|
||||||
$step,
|
|
||||||
$filter);
|
|
||||||
$options = array();
|
|
||||||
foreach ($available_artifacts as $key => $type) {
|
|
||||||
$options[$key] = $key;
|
|
||||||
}
|
|
||||||
$control = id(new AphrontFormSelectControl())
|
|
||||||
->setLabel($this->getReadableName($name, $opt))
|
|
||||||
->setName($name)
|
|
||||||
->setValue($value)
|
|
||||||
->setOptions($options);
|
|
||||||
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(
|
$form->appendChild(
|
||||||
id(new AphrontFormSubmitControl())
|
id(new AphrontFormSubmitControl())
|
||||||
|
@ -115,7 +69,7 @@ final class HarbormasterStepEditController
|
||||||
|
|
||||||
$box = id(new PHUIObjectBoxView())
|
$box = id(new PHUIObjectBoxView())
|
||||||
->setHeaderText('Edit Step: '.$implementation->getName())
|
->setHeaderText('Edit Step: '.$implementation->getName())
|
||||||
->setValidationException(null)
|
->setValidationException($validation_exception)
|
||||||
->setForm($form);
|
->setForm($form);
|
||||||
|
|
||||||
$crumbs = $this->buildApplicationCrumbs();
|
$crumbs = $this->buildApplicationCrumbs();
|
||||||
|
@ -127,11 +81,23 @@ final class HarbormasterStepEditController
|
||||||
|
|
||||||
$variables = $this->renderBuildVariablesTable();
|
$variables = $this->renderBuildVariablesTable();
|
||||||
|
|
||||||
|
$xactions = id(new HarbormasterBuildStepTransactionQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withObjectPHIDs(array($step->getPHID()))
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$xaction_view = id(new PhabricatorApplicationTransactionView())
|
||||||
|
->setUser($viewer)
|
||||||
|
->setObjectPHID($step->getPHID())
|
||||||
|
->setTransactions($xactions)
|
||||||
|
->setShouldTerminate(true);
|
||||||
|
|
||||||
return $this->buildApplicationPage(
|
return $this->buildApplicationPage(
|
||||||
array(
|
array(
|
||||||
$crumbs,
|
$crumbs,
|
||||||
$box,
|
$box,
|
||||||
$variables,
|
$variables,
|
||||||
|
$xaction_view,
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
'title' => $implementation->getName(),
|
'title' => $implementation->getName(),
|
||||||
|
@ -139,31 +105,6 @@ final class HarbormasterStepEditController
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
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:
|
|
||||||
case BuildStepImplementation::SETTING_TYPE_ARTIFACT:
|
|
||||||
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."'.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function renderBuildVariablesTable() {
|
private function renderBuildVariablesTable() {
|
||||||
$viewer = $this->getRequest()->getUser();
|
$viewer = $this->getRequest()->getUser();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class HarbormasterBuildStepCoreCustomField
|
||||||
|
extends HarbormasterBuildStepCustomField
|
||||||
|
implements PhabricatorStandardCustomFieldInterface {
|
||||||
|
|
||||||
|
public function getStandardCustomFieldNamespace() {
|
||||||
|
return 'harbormaster:core';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createFields($object) {
|
||||||
|
$specs = $object->getStepImplementation()->getFieldSpecifications();
|
||||||
|
return PhabricatorStandardCustomField::buildStandardFields($this, $specs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shouldUseStorage() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function readValueFromObject(PhabricatorCustomFieldInterface $object) {
|
||||||
|
$key = $this->getProxy()->getRawStandardFieldKey();
|
||||||
|
$this->setValueFromStorage($object->getDetail($key));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyApplicationTransactionInternalEffects(
|
||||||
|
PhabricatorApplicationTransaction $xaction) {
|
||||||
|
$object = $this->getObject();
|
||||||
|
$key = $this->getProxy()->getRawStandardFieldKey();
|
||||||
|
|
||||||
|
$this->setValueFromApplicationTransactions($xaction->getNewValue());
|
||||||
|
$value = $this->getValueForStorage();
|
||||||
|
|
||||||
|
$object->setDetail($key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyApplicationTransactionExternalEffects(
|
||||||
|
PhabricatorApplicationTransaction $xaction) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
abstract class HarbormasterBuildStepCustomField
|
||||||
|
extends PhabricatorCustomField {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class HarbormasterBuildStepEditor
|
||||||
|
extends PhabricatorApplicationTransactionEditor {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class HarbormasterBuildStepTransactionQuery
|
||||||
|
extends PhabricatorApplicationTransactionQuery {
|
||||||
|
|
||||||
|
public function getTemplateApplicationTransaction() {
|
||||||
|
return new HarbormasterBuildStepTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2,16 +2,9 @@
|
||||||
|
|
||||||
abstract class BuildStepImplementation {
|
abstract class BuildStepImplementation {
|
||||||
|
|
||||||
private $settings;
|
|
||||||
|
|
||||||
const SETTING_TYPE_STRING = 'string';
|
|
||||||
const SETTING_TYPE_INTEGER = 'integer';
|
|
||||||
const SETTING_TYPE_BOOLEAN = 'boolean';
|
|
||||||
const SETTING_TYPE_ARTIFACT = 'artifact';
|
|
||||||
|
|
||||||
public static function getImplementations() {
|
public static function getImplementations() {
|
||||||
$symbols = id(new PhutilSymbolLoader())
|
$symbols = id(new PhutilSymbolLoader())
|
||||||
->setAncestorClass("BuildStepImplementation")
|
->setAncestorClass('BuildStepImplementation')
|
||||||
->setConcreteOnly(true)
|
->setConcreteOnly(true)
|
||||||
->selectAndLoadSymbols();
|
->selectAndLoadSymbols();
|
||||||
return ipull($symbols, 'name');
|
return ipull($symbols, 'name');
|
||||||
|
@ -33,7 +26,7 @@ abstract class BuildStepImplementation {
|
||||||
* The description of the implementation, based on the current settings.
|
* The description of the implementation, based on the current settings.
|
||||||
*/
|
*/
|
||||||
public function getDescription() {
|
public function getDescription() {
|
||||||
return '';
|
return $this->getGenericDescription();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,44 +47,13 @@ abstract class BuildStepImplementation {
|
||||||
return idx($this->settings, $key, $default);
|
return idx($this->settings, $key, $default);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate the current settings of this build step.
|
|
||||||
*/
|
|
||||||
public function validateSettings() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the settings for this build step implementation from a build
|
* Loads the settings for this build step implementation from a build
|
||||||
* step or target.
|
* step or target.
|
||||||
*/
|
*/
|
||||||
public final function loadSettings($build_object) {
|
public final function loadSettings($build_object) {
|
||||||
$this->settings = array();
|
$this->settings = $build_object->getDetails();
|
||||||
$this->validateSettingDefinitions();
|
return $this;
|
||||||
foreach ($this->getSettingDefinitions() as $name => $opt) {
|
|
||||||
$this->settings[$name] = $build_object->getDetail($name);
|
|
||||||
}
|
|
||||||
return $this->settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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() {
|
|
||||||
return array();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -197,4 +159,8 @@ abstract class BuildStepImplementation {
|
||||||
return call_user_func($function, $pattern, $argv);
|
return call_user_func($function, $pattern, $argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getFieldSpecifications() {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,22 +74,6 @@ final class CommandBuildStepImplementation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function validateSettings() {
|
|
||||||
$settings = $this->getSettings();
|
|
||||||
|
|
||||||
if ($settings['command'] === null || !is_string($settings['command'])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if ($settings['hostartifact'] === null ||
|
|
||||||
!is_string($settings['hostartifact'])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Check if the host artifact is provided by previous build steps.
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getArtifactInputs() {
|
public function getArtifactInputs() {
|
||||||
return array(
|
return array(
|
||||||
array(
|
array(
|
||||||
|
@ -100,19 +84,19 @@ final class CommandBuildStepImplementation
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSettingDefinitions() {
|
public function getFieldSpecifications() {
|
||||||
return array(
|
return array(
|
||||||
'command' => array(
|
'command' => array(
|
||||||
'name' => 'Command',
|
'name' => pht('Command'),
|
||||||
'description' => 'The command to execute on the remote machine.',
|
'type' => 'text',
|
||||||
'type' => BuildStepImplementation::SETTING_TYPE_STRING),
|
'required' => true,
|
||||||
|
),
|
||||||
'hostartifact' => array(
|
'hostartifact' => array(
|
||||||
'name' => 'Host Artifact',
|
'name' => pht('Host'),
|
||||||
'description' =>
|
'type' => 'text',
|
||||||
'The host artifact that determines what machine the command '.
|
'required' => true,
|
||||||
'will run on.',
|
),
|
||||||
'type' => BuildStepImplementation::SETTING_TYPE_ARTIFACT,
|
);
|
||||||
'artifact_type' => HarbormasterBuildArtifact::TYPE_HOST));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,10 +34,7 @@ final class HarbormasterHTTPRequestBuildStepImplementation
|
||||||
$log_body = $build->createLog($build_target, $uri, 'http-body');
|
$log_body = $build->createLog($build_target, $uri, 'http-body');
|
||||||
$start = $log_body->start();
|
$start = $log_body->start();
|
||||||
|
|
||||||
$method = 'POST';
|
$method = nonempty(idx($settings, 'method'), 'POST');
|
||||||
if ($settings['method'] !== '') {
|
|
||||||
$method = $settings['method'];
|
|
||||||
}
|
|
||||||
|
|
||||||
list($status, $body, $headers) = id(new HTTPSFuture($uri))
|
list($status, $body, $headers) = id(new HTTPSFuture($uri))
|
||||||
->setMethod($method)
|
->setMethod($method)
|
||||||
|
@ -52,42 +49,17 @@ final class HarbormasterHTTPRequestBuildStepImplementation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function validateSettings() {
|
public function getFieldSpecifications() {
|
||||||
$settings = $this->getSettings();
|
|
||||||
|
|
||||||
if ($settings['uri'] === null || !is_string($settings['uri'])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$methods = array(
|
|
||||||
'GET' => true,
|
|
||||||
'POST' => true,
|
|
||||||
'DELETE' => true,
|
|
||||||
'PUT' => true,
|
|
||||||
);
|
|
||||||
|
|
||||||
$method = idx($settings, 'method');
|
|
||||||
if (strlen($method)) {
|
|
||||||
if (empty($methods[$method])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSettingDefinitions() {
|
|
||||||
return array(
|
return array(
|
||||||
'uri' => array(
|
'uri' => array(
|
||||||
'name' => 'URI',
|
'name' => pht('URI'),
|
||||||
'description' => pht('The URI to request.'),
|
'type' => 'text',
|
||||||
'type' => BuildStepImplementation::SETTING_TYPE_STRING,
|
'required' => true,
|
||||||
),
|
),
|
||||||
'method' => array(
|
'method' => array(
|
||||||
'name' => 'Method',
|
'name' => pht('HTTP Method'),
|
||||||
'description' =>
|
'type' => 'select',
|
||||||
pht('Request type. Should be GET, POST, PUT, or DELETE.'),
|
'options' => array_fuse(array('POST', 'GET', 'PUT', 'DELETE')),
|
||||||
'type' => BuildStepImplementation::SETTING_TYPE_STRING,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,19 +51,6 @@ final class LeaseHostBuildStepImplementation
|
||||||
$artifact->save();
|
$artifact->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function validateSettings() {
|
|
||||||
$settings = $this->getSettings();
|
|
||||||
|
|
||||||
if ($settings['name'] === null || !is_string($settings['name'])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if ($settings['platform'] === null || !is_string($settings['platform'])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getArtifactOutputs() {
|
public function getArtifactOutputs() {
|
||||||
return array(
|
return array(
|
||||||
array(
|
array(
|
||||||
|
@ -74,17 +61,19 @@ final class LeaseHostBuildStepImplementation
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSettingDefinitions() {
|
public function getFieldSpecifications() {
|
||||||
return array(
|
return array(
|
||||||
'name' => array(
|
'name' => array(
|
||||||
'name' => 'Artifact Name',
|
'name' => pht('Artifact Name'),
|
||||||
'description' =>
|
'type' => 'text',
|
||||||
'The name of the artifact to reference in future build steps.',
|
'required' => true,
|
||||||
'type' => BuildStepImplementation::SETTING_TYPE_STRING),
|
),
|
||||||
'platform' => array(
|
'platform' => array(
|
||||||
'name' => 'Platform',
|
'name' => pht('Platform'),
|
||||||
'description' => 'The platform of the host.',
|
'type' => 'text',
|
||||||
'type' => BuildStepImplementation::SETTING_TYPE_STRING));
|
'required' => true,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,22 +57,6 @@ final class PublishFragmentBuildStepImplementation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function validateSettings() {
|
|
||||||
$settings = $this->getSettings();
|
|
||||||
|
|
||||||
if ($settings['path'] === null || !is_string($settings['path'])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if ($settings['artifact'] === null ||
|
|
||||||
!is_string($settings['artifact'])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Check if the file artifact is provided by previous build steps.
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getArtifactInputs() {
|
public function getArtifactInputs() {
|
||||||
return array(
|
return array(
|
||||||
array(
|
array(
|
||||||
|
@ -83,19 +67,19 @@ final class PublishFragmentBuildStepImplementation
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSettingDefinitions() {
|
public function getFieldSpecifications() {
|
||||||
return array(
|
return array(
|
||||||
'path' => array(
|
'path' => array(
|
||||||
'name' => 'Path',
|
'name' => pht('Path'),
|
||||||
'description' =>
|
'type' => 'text',
|
||||||
'The path of the fragment that will be published.',
|
'required' => true,
|
||||||
'type' => BuildStepImplementation::SETTING_TYPE_STRING),
|
),
|
||||||
'artifact' => array(
|
'artifact' => array(
|
||||||
'name' => 'File Artifact',
|
'name' => pht('File Artifact'),
|
||||||
'description' =>
|
'type' => 'text',
|
||||||
'The file artifact that will be published to Phragment.',
|
'required' => true,
|
||||||
'type' => BuildStepImplementation::SETTING_TYPE_ARTIFACT,
|
),
|
||||||
'artifact_type' => HarbormasterBuildArtifact::TYPE_FILE));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,24 +25,15 @@ final class SleepBuildStepImplementation extends BuildStepImplementation {
|
||||||
sleep($settings['seconds']);
|
sleep($settings['seconds']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function validateSettings() {
|
public function getFieldSpecifications() {
|
||||||
$settings = $this->getSettings();
|
|
||||||
|
|
||||||
if ($settings['seconds'] === null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!is_int($settings['seconds'])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSettingDefinitions() {
|
|
||||||
return array(
|
return array(
|
||||||
'seconds' => array(
|
'seconds' => array(
|
||||||
'name' => 'Seconds',
|
'name' => pht('Seconds'),
|
||||||
'description' => 'The number of seconds to sleep for.',
|
'type' => 'int',
|
||||||
'type' => BuildStepImplementation::SETTING_TYPE_INTEGER));
|
'required' => true,
|
||||||
|
'caption' => pht('The number of seconds to sleep for.'),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,25 +51,6 @@ final class UploadArtifactBuildStepImplementation
|
||||||
$artifact->save();
|
$artifact->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function validateSettings() {
|
|
||||||
$settings = $this->getSettings();
|
|
||||||
|
|
||||||
if ($settings['path'] === null || !is_string($settings['path'])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if ($settings['name'] === null || !is_string($settings['name'])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if ($settings['hostartifact'] === null ||
|
|
||||||
!is_string($settings['hostartifact'])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Check if the host artifact is provided by previous build steps.
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getArtifactInputs() {
|
public function getArtifactInputs() {
|
||||||
return array(
|
return array(
|
||||||
array(
|
array(
|
||||||
|
@ -90,28 +71,24 @@ final class UploadArtifactBuildStepImplementation
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSettingDefinitions() {
|
public function getFieldSpecifications() {
|
||||||
return array(
|
return array(
|
||||||
'path' => array(
|
'path' => array(
|
||||||
'name' => 'Path',
|
'name' => pht('Path'),
|
||||||
'description' =>
|
'type' => 'text',
|
||||||
'The path of the file that should be retrieved. Note that on '.
|
'required' => true,
|
||||||
'Windows machines running FreeSSHD, this path will be relative '.
|
),
|
||||||
'to the SFTP root path (configured under the SFTP tab). You can '.
|
|
||||||
'not specify an absolute path for Windows machines.',
|
|
||||||
'type' => BuildStepImplementation::SETTING_TYPE_STRING),
|
|
||||||
'name' => array(
|
'name' => array(
|
||||||
'name' => 'Local Name',
|
'name' => pht('Local Name'),
|
||||||
'description' =>
|
'type' => 'text',
|
||||||
'The name for the file when it is stored in Phabricator.',
|
'required' => true,
|
||||||
'type' => BuildStepImplementation::SETTING_TYPE_STRING),
|
),
|
||||||
'hostartifact' => array(
|
'hostartifact' => array(
|
||||||
'name' => 'Host Artifact',
|
'name' => pht('Host Artifact'),
|
||||||
'description' =>
|
'type' => 'text',
|
||||||
'The host artifact that determines what machine the command '.
|
'required' => true,
|
||||||
'will run on.',
|
),
|
||||||
'type' => BuildStepImplementation::SETTING_TYPE_ARTIFACT,
|
);
|
||||||
'artifact_type' => HarbormasterBuildArtifact::TYPE_HOST));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
final class HarbormasterBuildStep extends HarbormasterDAO
|
final class HarbormasterBuildStep extends HarbormasterDAO
|
||||||
implements PhabricatorPolicyInterface {
|
implements
|
||||||
|
PhabricatorPolicyInterface,
|
||||||
|
PhabricatorCustomFieldInterface {
|
||||||
|
|
||||||
protected $buildPlanPHID;
|
protected $buildPlanPHID;
|
||||||
protected $className;
|
protected $className;
|
||||||
|
@ -9,6 +11,7 @@ final class HarbormasterBuildStep extends HarbormasterDAO
|
||||||
protected $sequence;
|
protected $sequence;
|
||||||
|
|
||||||
private $buildPlan = self::ATTACHABLE;
|
private $buildPlan = self::ATTACHABLE;
|
||||||
|
private $customFields = self::ATTACHABLE;
|
||||||
|
|
||||||
public function getConfiguration() {
|
public function getConfiguration() {
|
||||||
return array(
|
return array(
|
||||||
|
@ -83,4 +86,25 @@ final class HarbormasterBuildStep extends HarbormasterDAO
|
||||||
public function describeAutomaticCapability($capability) {
|
public function describeAutomaticCapability($capability) {
|
||||||
return pht('A build step has the same policies as its build plan.');
|
return pht('A build step has the same policies as its build plan.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -( PhabricatorCustomFieldInterface )------------------------------------ */
|
||||||
|
|
||||||
|
public function getCustomFieldSpecificationForRole($role) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCustomFieldBaseClass() {
|
||||||
|
return 'HarbormasterBuildStepCustomField';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCustomFields() {
|
||||||
|
return $this->assertAttached($this->customFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
|
||||||
|
$this->customFields = $fields;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class HarbormasterBuildStepTransaction
|
||||||
|
extends PhabricatorApplicationTransaction {
|
||||||
|
|
||||||
|
public function getApplicationName() {
|
||||||
|
return 'harbormaster';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getApplicationTransactionType() {
|
||||||
|
return HarbormasterPHIDTypeBuildStep::TYPECONST;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -37,14 +37,9 @@ final class HarbormasterTargetWorker extends HarbormasterWorker {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$implementation = $target->getImplementation();
|
$implementation = $target->getImplementation();
|
||||||
if (!$implementation->validateSettings()) {
|
|
||||||
$target->setTargetStatus(HarbormasterBuildTarget::STATUS_FAILED);
|
|
||||||
$target->save();
|
|
||||||
} else {
|
|
||||||
$implementation->execute($build, $target);
|
$implementation->execute($build, $target);
|
||||||
$target->setTargetStatus(HarbormasterBuildTarget::STATUS_PASSED);
|
$target->setTargetStatus(HarbormasterBuildTarget::STATUS_PASSED);
|
||||||
$target->save();
|
$target->save();
|
||||||
}
|
|
||||||
} catch (Exception $ex) {
|
} catch (Exception $ex) {
|
||||||
phlog($ex);
|
phlog($ex);
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ final class ManiphestConfiguredCustomField
|
||||||
return 'maniphest';
|
return 'maniphest';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createFields() {
|
public function createFields($object) {
|
||||||
$config = PhabricatorEnv::getEnvConfig(
|
$config = PhabricatorEnv::getEnvConfig(
|
||||||
'maniphest.custom-field-definitions',
|
'maniphest.custom-field-definitions',
|
||||||
array());
|
array());
|
||||||
|
|
|
@ -8,7 +8,7 @@ final class PhabricatorUserConfiguredCustomField
|
||||||
return 'user';
|
return 'user';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createFields() {
|
public function createFields($object) {
|
||||||
return PhabricatorStandardCustomField::buildStandardFields(
|
return PhabricatorStandardCustomField::buildStandardFields(
|
||||||
$this,
|
$this,
|
||||||
PhabricatorEnv::getEnvConfig('user.custom-field-definitions', array()));
|
PhabricatorEnv::getEnvConfig('user.custom-field-definitions', array()));
|
||||||
|
|
|
@ -8,7 +8,7 @@ final class PhabricatorProjectConfiguredCustomField
|
||||||
return 'project';
|
return 'project';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createFields() {
|
public function createFields($object) {
|
||||||
return PhabricatorStandardCustomField::buildStandardFields(
|
return PhabricatorStandardCustomField::buildStandardFields(
|
||||||
$this,
|
$this,
|
||||||
PhabricatorEnv::getEnvConfig(
|
PhabricatorEnv::getEnvConfig(
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
final class PhabricatorProjectDescriptionField
|
final class PhabricatorProjectDescriptionField
|
||||||
extends PhabricatorProjectStandardCustomField {
|
extends PhabricatorProjectStandardCustomField {
|
||||||
|
|
||||||
public function createFields() {
|
public function createFields($object) {
|
||||||
return PhabricatorStandardCustomField::buildStandardFields(
|
return PhabricatorStandardCustomField::buildStandardFields(
|
||||||
$this,
|
$this,
|
||||||
array(
|
array(
|
||||||
|
|
|
@ -43,9 +43,14 @@ final class PhabricatorCustomFieldConfigOptionType
|
||||||
foreach ($faux_spec as $key => $spec) {
|
foreach ($faux_spec as $key => $spec) {
|
||||||
unset($faux_spec[$key]['disabled']);
|
unset($faux_spec[$key]['disabled']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: We might need to build a real object here eventually.
|
||||||
|
$faux_object = null;
|
||||||
|
|
||||||
$fields = PhabricatorCustomField::buildFieldList(
|
$fields = PhabricatorCustomField::buildFieldList(
|
||||||
$field_base_class,
|
$field_base_class,
|
||||||
$faux_spec);
|
$faux_spec,
|
||||||
|
$faux_object);
|
||||||
|
|
||||||
$list_id = celerity_generate_unique_node_id();
|
$list_id = celerity_generate_unique_node_id();
|
||||||
$input_id = celerity_generate_unique_node_id();
|
$input_id = celerity_generate_unique_node_id();
|
||||||
|
|
|
@ -60,7 +60,10 @@ abstract class PhabricatorCustomField {
|
||||||
"object of class '{$obj_class}'.");
|
"object of class '{$obj_class}'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
$fields = PhabricatorCustomField::buildFieldList($base_class, $spec);
|
$fields = PhabricatorCustomField::buildFieldList(
|
||||||
|
$base_class,
|
||||||
|
$spec,
|
||||||
|
$object);
|
||||||
|
|
||||||
foreach ($fields as $key => $field) {
|
foreach ($fields as $key => $field) {
|
||||||
if (!$field->shouldEnableForRole($role)) {
|
if (!$field->shouldEnableForRole($role)) {
|
||||||
|
@ -97,7 +100,7 @@ abstract class PhabricatorCustomField {
|
||||||
/**
|
/**
|
||||||
* @task apps
|
* @task apps
|
||||||
*/
|
*/
|
||||||
public static function buildFieldList($base_class, array $spec) {
|
public static function buildFieldList($base_class, array $spec, $object) {
|
||||||
$field_objects = id(new PhutilSymbolLoader())
|
$field_objects = id(new PhutilSymbolLoader())
|
||||||
->setAncestorClass($base_class)
|
->setAncestorClass($base_class)
|
||||||
->loadObjects();
|
->loadObjects();
|
||||||
|
@ -106,7 +109,7 @@ abstract class PhabricatorCustomField {
|
||||||
$from_map = array();
|
$from_map = array();
|
||||||
foreach ($field_objects as $field_object) {
|
foreach ($field_objects as $field_object) {
|
||||||
$current_class = get_class($field_object);
|
$current_class = get_class($field_object);
|
||||||
foreach ($field_object->createFields() as $field) {
|
foreach ($field_object->createFields($object) as $field) {
|
||||||
$key = $field->getFieldKey();
|
$key = $field->getFieldKey();
|
||||||
if (isset($fields[$key])) {
|
if (isset($fields[$key])) {
|
||||||
$original_class = $from_map[$key];
|
$original_class = $from_map[$key];
|
||||||
|
@ -200,10 +203,11 @@ abstract class PhabricatorCustomField {
|
||||||
* For general implementations, the general field implementation can return
|
* For general implementations, the general field implementation can return
|
||||||
* multiple field instances here.
|
* multiple field instances here.
|
||||||
*
|
*
|
||||||
|
* @param object The object to create fields for.
|
||||||
* @return list<PhabricatorCustomField> List of fields.
|
* @return list<PhabricatorCustomField> List of fields.
|
||||||
* @task core
|
* @task core
|
||||||
*/
|
*/
|
||||||
public function createFields() {
|
public function createFields($object) {
|
||||||
return array($this);
|
return array($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,9 +243,9 @@ abstract class PhabricatorCustomField {
|
||||||
* @task core
|
* @task core
|
||||||
*/
|
*/
|
||||||
public function shouldEnableForRole($role) {
|
public function shouldEnableForRole($role) {
|
||||||
if ($this->proxy) {
|
|
||||||
return $this->proxy->shouldEnableForRole($role);
|
// NOTE: All of these calls proxy individually, so we don't need to
|
||||||
}
|
// proxy this call as a whole.
|
||||||
|
|
||||||
switch ($role) {
|
switch ($role) {
|
||||||
case self::ROLE_APPLICATIONTRANSACTIONS:
|
case self::ROLE_APPLICATIONTRANSACTIONS:
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
abstract class PhabricatorStandardCustomField
|
abstract class PhabricatorStandardCustomField
|
||||||
extends PhabricatorCustomField {
|
extends PhabricatorCustomField {
|
||||||
|
|
||||||
|
private $rawKey;
|
||||||
private $fieldKey;
|
private $fieldKey;
|
||||||
private $fieldName;
|
private $fieldName;
|
||||||
private $fieldValue;
|
private $fieldValue;
|
||||||
|
@ -40,6 +41,7 @@ abstract class PhabricatorStandardCustomField
|
||||||
|
|
||||||
$template = clone $template;
|
$template = clone $template;
|
||||||
$standard = id(clone $types[$type])
|
$standard = id(clone $types[$type])
|
||||||
|
->setRawStandardFieldKey($key)
|
||||||
->setFieldKey($full_key)
|
->setFieldKey($full_key)
|
||||||
->setFieldConfig($value)
|
->setFieldConfig($value)
|
||||||
->setApplicationField($template);
|
->setApplicationField($template);
|
||||||
|
@ -142,6 +144,15 @@ abstract class PhabricatorStandardCustomField
|
||||||
return $this->required;
|
return $this->required;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setRawStandardFieldKey($raw_key) {
|
||||||
|
$this->rawKey = $raw_key;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRawStandardFieldKey() {
|
||||||
|
return $this->rawKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -( PhabricatorCustomField )--------------------------------------------- */
|
/* -( PhabricatorCustomField )--------------------------------------------- */
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue