Page MenuHomePhabricator

D7521.id17007.diff
No OneTemporary

D7521.id17007.diff

Index: resources/sql/patches/20131107.buildlog.sql
===================================================================
--- /dev/null
+++ resources/sql/patches/20131107.buildlog.sql
@@ -0,0 +1,26 @@
+CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildlog (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ buildPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ buildStepPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ logSource VARCHAR(255) NULL COLLATE utf8_bin,
+ logType VARCHAR(255) NULL COLLATE utf8_bin,
+ duration INT UNSIGNED NULL,
+ live BOOLEAN NOT NULL,
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL,
+ KEY `key_build` (buildPHID, buildStepPHID),
+ UNIQUE KEY `key_phid` (phid)
+) ENGINE=InnoDB, COLLATE utf8_general_ci;
+
+ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_build
+ADD COLUMN cancelRequested BOOLEAN NOT NULL;
+
+CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildlogchunk (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ logID INT UNSIGNED NOT NULL COLLATE utf8_bin,
+ encoding VARCHAR(30) NOT NULL COLLATE utf8_bin,
+ size LONG NULL,
+ chunk LONGBLOB NOT NULL,
+ KEY `key_log` (logID)
+) ENGINE=InnoDB, COLLATE utf8_general_ci;
Index: src/__celerity_resource_map__.php
===================================================================
--- src/__celerity_resource_map__.php
+++ src/__celerity_resource_map__.php
@@ -569,22 +569,22 @@
),
'/rsrc/image/sprite-apps-X2.png' =>
array(
- 'hash' => '68bbb3f409d0eb42d65dd94769813044',
- 'uri' => '/res/68bbb3f4/rsrc/image/sprite-apps-X2.png',
+ 'hash' => '67e8a6bf2d7fbb0b7961f1a0dcf8592b',
+ 'uri' => '/res/67e8a6bf/rsrc/image/sprite-apps-X2.png',
'disk' => '/rsrc/image/sprite-apps-X2.png',
'type' => 'png',
),
'/rsrc/image/sprite-apps-large-X2.png' =>
array(
- 'hash' => '15368afbac0e1402c20f99f3166cdb11',
- 'uri' => '/res/15368afb/rsrc/image/sprite-apps-large-X2.png',
+ 'hash' => '9bed1778022e2bd25d658842be54844d',
+ 'uri' => '/res/9bed1778/rsrc/image/sprite-apps-large-X2.png',
'disk' => '/rsrc/image/sprite-apps-large-X2.png',
'type' => 'png',
),
'/rsrc/image/sprite-apps-large.png' =>
array(
- 'hash' => 'b1f1de55803cf22eb3beb391fff17b04',
- 'uri' => '/res/b1f1de55/rsrc/image/sprite-apps-large.png',
+ 'hash' => '518d6e5487c8d3758921ad85c1bb7d60',
+ 'uri' => '/res/518d6e54/rsrc/image/sprite-apps-large.png',
'disk' => '/rsrc/image/sprite-apps-large.png',
'type' => 'png',
),
@@ -597,8 +597,8 @@
),
'/rsrc/image/sprite-apps.png' =>
array(
- 'hash' => 'bf7feaae848d44a461e63123c28e402f',
- 'uri' => '/res/bf7feaae/rsrc/image/sprite-apps.png',
+ 'hash' => '9024ab95247f936f41c0b51c75e2e228',
+ 'uri' => '/res/9024ab95/rsrc/image/sprite-apps.png',
'disk' => '/rsrc/image/sprite-apps.png',
'type' => 'png',
),
@@ -1197,7 +1197,7 @@
),
'herald-rule-editor' =>
array(
- 'uri' => '/res/a561eb19/rsrc/js/application/herald/HeraldRuleEditor.js',
+ 'uri' => '/res/928275b4/rsrc/js/application/herald/HeraldRuleEditor.js',
'type' => 'js',
'requires' =>
array(
@@ -4183,7 +4183,7 @@
),
'sprite-apps-css' =>
array(
- 'uri' => '/res/37c55e75/rsrc/css/sprite-apps.css',
+ 'uri' => '/res/774f4bad/rsrc/css/sprite-apps.css',
'type' => 'css',
'requires' =>
array(
@@ -4192,7 +4192,7 @@
),
'sprite-apps-large-css' =>
array(
- 'uri' => '/res/8ddded36/rsrc/css/sprite-apps-large.css',
+ 'uri' => '/res/b547fab1/rsrc/css/sprite-apps-large.css',
'type' => 'css',
'requires' =>
array(
@@ -4328,7 +4328,7 @@
), array(
'packages' =>
array(
- 'f350af41' =>
+ 'f0d63822' =>
array(
'name' => 'core.pkg.css',
'symbols' =>
@@ -4377,7 +4377,7 @@
41 => 'phabricator-tag-view-css',
42 => 'phui-list-view-css',
),
- 'uri' => '/res/pkg/f350af41/core.pkg.css',
+ 'uri' => '/res/pkg/f0d63822/core.pkg.css',
'type' => 'css',
),
'2c1dba03' =>
@@ -4569,15 +4569,15 @@
),
'reverse' =>
array(
- 'aphront-dialog-view-css' => 'f350af41',
- 'aphront-error-view-css' => 'f350af41',
- 'aphront-list-filter-view-css' => 'f350af41',
- 'aphront-pager-view-css' => 'f350af41',
- 'aphront-panel-view-css' => 'f350af41',
- 'aphront-table-view-css' => 'f350af41',
- 'aphront-tokenizer-control-css' => 'f350af41',
- 'aphront-tooltip-css' => 'f350af41',
- 'aphront-typeahead-control-css' => 'f350af41',
+ 'aphront-dialog-view-css' => 'f0d63822',
+ 'aphront-error-view-css' => 'f0d63822',
+ 'aphront-list-filter-view-css' => 'f0d63822',
+ 'aphront-pager-view-css' => 'f0d63822',
+ 'aphront-panel-view-css' => 'f0d63822',
+ 'aphront-table-view-css' => 'f0d63822',
+ 'aphront-tokenizer-control-css' => 'f0d63822',
+ 'aphront-tooltip-css' => 'f0d63822',
+ 'aphront-typeahead-control-css' => 'f0d63822',
'differential-changeset-view-css' => '1084b12b',
'differential-core-view-css' => '1084b12b',
'differential-inline-comment-editor' => '5e9e5c4e',
@@ -4591,7 +4591,7 @@
'differential-table-of-contents-css' => '1084b12b',
'diffusion-commit-view-css' => '7aa115b4',
'diffusion-icons-css' => '7aa115b4',
- 'global-drag-and-drop-css' => 'f350af41',
+ 'global-drag-and-drop-css' => 'f0d63822',
'inline-comment-summary-css' => '1084b12b',
'javelin-aphlict' => '2c1dba03',
'javelin-behavior' => '3e3be199',
@@ -4666,56 +4666,56 @@
'javelin-util' => '3e3be199',
'javelin-vector' => '3e3be199',
'javelin-workflow' => '3e3be199',
- 'lightbox-attachment-css' => 'f350af41',
+ 'lightbox-attachment-css' => 'f0d63822',
'maniphest-task-summary-css' => '49898640',
- 'phabricator-action-list-view-css' => 'f350af41',
- 'phabricator-application-launch-view-css' => 'f350af41',
+ 'phabricator-action-list-view-css' => 'f0d63822',
+ 'phabricator-application-launch-view-css' => 'f0d63822',
'phabricator-busy' => '2c1dba03',
'phabricator-content-source-view-css' => '1084b12b',
- 'phabricator-core-css' => 'f350af41',
- 'phabricator-crumbs-view-css' => 'f350af41',
+ 'phabricator-core-css' => 'f0d63822',
+ 'phabricator-crumbs-view-css' => 'f0d63822',
'phabricator-drag-and-drop-file-upload' => '5e9e5c4e',
'phabricator-dropdown-menu' => '2c1dba03',
'phabricator-file-upload' => '2c1dba03',
- 'phabricator-filetree-view-css' => 'f350af41',
- 'phabricator-flag-css' => 'f350af41',
+ 'phabricator-filetree-view-css' => 'f0d63822',
+ 'phabricator-flag-css' => 'f0d63822',
'phabricator-hovercard' => '2c1dba03',
- 'phabricator-jump-nav' => 'f350af41',
+ 'phabricator-jump-nav' => 'f0d63822',
'phabricator-keyboard-shortcut' => '2c1dba03',
'phabricator-keyboard-shortcut-manager' => '2c1dba03',
- 'phabricator-main-menu-view' => 'f350af41',
+ 'phabricator-main-menu-view' => 'f0d63822',
'phabricator-menu-item' => '2c1dba03',
- 'phabricator-nav-view-css' => 'f350af41',
+ 'phabricator-nav-view-css' => 'f0d63822',
'phabricator-notification' => '2c1dba03',
- 'phabricator-notification-css' => 'f350af41',
- 'phabricator-notification-menu-css' => 'f350af41',
+ 'phabricator-notification-css' => 'f0d63822',
+ 'phabricator-notification-menu-css' => 'f0d63822',
'phabricator-object-selector-css' => '1084b12b',
'phabricator-phtize' => '2c1dba03',
'phabricator-prefab' => '2c1dba03',
'phabricator-project-tag-css' => '49898640',
- 'phabricator-remarkup-css' => 'f350af41',
+ 'phabricator-remarkup-css' => 'f0d63822',
'phabricator-shaped-request' => '5e9e5c4e',
- 'phabricator-side-menu-view-css' => 'f350af41',
- 'phabricator-standard-page-view' => 'f350af41',
- 'phabricator-tag-view-css' => 'f350af41',
+ 'phabricator-side-menu-view-css' => 'f0d63822',
+ 'phabricator-standard-page-view' => 'f0d63822',
+ 'phabricator-tag-view-css' => 'f0d63822',
'phabricator-textareautils' => '2c1dba03',
'phabricator-tooltip' => '2c1dba03',
- 'phabricator-transaction-view-css' => 'f350af41',
- 'phabricator-zindex-css' => 'f350af41',
- 'phui-button-css' => 'f350af41',
- 'phui-form-css' => 'f350af41',
- 'phui-form-view-css' => 'f350af41',
- 'phui-header-view-css' => 'f350af41',
- 'phui-icon-view-css' => 'f350af41',
- 'phui-list-view-css' => 'f350af41',
- 'phui-object-item-list-view-css' => 'f350af41',
- 'phui-property-list-view-css' => 'f350af41',
- 'phui-spacing-css' => 'f350af41',
- 'sprite-apps-large-css' => 'f350af41',
- 'sprite-gradient-css' => 'f350af41',
- 'sprite-icons-css' => 'f350af41',
- 'sprite-menu-css' => 'f350af41',
- 'sprite-status-css' => 'f350af41',
- 'syntax-highlighting-css' => 'f350af41',
+ 'phabricator-transaction-view-css' => 'f0d63822',
+ 'phabricator-zindex-css' => 'f0d63822',
+ 'phui-button-css' => 'f0d63822',
+ 'phui-form-css' => 'f0d63822',
+ 'phui-form-view-css' => 'f0d63822',
+ 'phui-header-view-css' => 'f0d63822',
+ 'phui-icon-view-css' => 'f0d63822',
+ 'phui-list-view-css' => 'f0d63822',
+ 'phui-object-item-list-view-css' => 'f0d63822',
+ 'phui-property-list-view-css' => 'f0d63822',
+ 'phui-spacing-css' => 'f0d63822',
+ 'sprite-apps-large-css' => 'f0d63822',
+ 'sprite-gradient-css' => 'f0d63822',
+ 'sprite-icons-css' => 'f0d63822',
+ 'sprite-menu-css' => 'f0d63822',
+ 'sprite-status-css' => 'f0d63822',
+ 'syntax-highlighting-css' => 'f0d63822',
),
));
Index: src/__phutil_library_map__.php
===================================================================
--- src/__phutil_library_map__.php
+++ src/__phutil_library_map__.php
@@ -652,9 +652,11 @@
'FileReplyHandler' => 'applications/files/mail/FileReplyHandler.php',
'HarbormasterBuild' => 'applications/harbormaster/storage/build/HarbormasterBuild.php',
'HarbormasterBuildArtifact' => 'applications/harbormaster/storage/build/HarbormasterBuildArtifact.php',
+ 'HarbormasterBuildCancelController' => 'applications/harbormaster/controller/HarbormasterBuildCancelController.php',
'HarbormasterBuildItem' => 'applications/harbormaster/storage/build/HarbormasterBuildItem.php',
'HarbormasterBuildItemQuery' => 'applications/harbormaster/query/HarbormasterBuildItemQuery.php',
'HarbormasterBuildLog' => 'applications/harbormaster/storage/build/HarbormasterBuildLog.php',
+ 'HarbormasterBuildLogQuery' => 'applications/harbormaster/query/HarbormasterBuildLogQuery.php',
'HarbormasterBuildPlan' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php',
'HarbormasterBuildPlanEditor' => 'applications/harbormaster/editor/HarbormasterBuildPlanEditor.php',
'HarbormasterBuildPlanQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanQuery.php',
@@ -667,6 +669,7 @@
'HarbormasterBuildStepQuery' => 'applications/harbormaster/query/HarbormasterBuildStepQuery.php',
'HarbormasterBuildTarget' => 'applications/harbormaster/storage/build/HarbormasterBuildTarget.php',
'HarbormasterBuildTargetQuery' => 'applications/harbormaster/query/HarbormasterBuildTargetQuery.php',
+ 'HarbormasterBuildViewController' => 'applications/harbormaster/controller/HarbormasterBuildViewController.php',
'HarbormasterBuildWorker' => 'applications/harbormaster/worker/HarbormasterBuildWorker.php',
'HarbormasterBuildable' => 'applications/harbormaster/storage/HarbormasterBuildable.php',
'HarbormasterBuildableApplyController' => 'applications/harbormaster/controller/HarbormasterBuildableApplyController.php',
@@ -682,6 +685,7 @@
'HarbormasterObject' => 'applications/harbormaster/storage/HarbormasterObject.php',
'HarbormasterPHIDTypeBuild' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuild.php',
'HarbormasterPHIDTypeBuildItem' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildItem.php',
+ 'HarbormasterPHIDTypeBuildLog' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildLog.php',
'HarbormasterPHIDTypeBuildPlan' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildPlan.php',
'HarbormasterPHIDTypeBuildStep' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildStep.php',
'HarbormasterPHIDTypeBuildTarget' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildTarget.php',
@@ -1392,6 +1396,7 @@
'PhabricatorGlobalUploadTargetView' => 'applications/files/view/PhabricatorGlobalUploadTargetView.php',
'PhabricatorHandleObjectSelectorDataView' => 'applications/phid/handle/view/PhabricatorHandleObjectSelectorDataView.php',
'PhabricatorHandleQuery' => 'applications/phid/query/PhabricatorHandleQuery.php',
+ 'PhabricatorHarbormasterConfigOptions' => 'applications/harbormaster/config/PhabricatorHarbormasterConfigOptions.php',
'PhabricatorHash' => 'infrastructure/util/PhabricatorHash.php',
'PhabricatorHashTestCase' => 'infrastructure/util/__tests__/PhabricatorHashTestCase.php',
'PhabricatorHelpController' => 'applications/help/controller/PhabricatorHelpController.php',
@@ -2254,9 +2259,11 @@
'ReleephStatusFieldSpecification' => 'applications/releeph/field/specification/ReleephStatusFieldSpecification.php',
'ReleephSummaryFieldSpecification' => 'applications/releeph/field/specification/ReleephSummaryFieldSpecification.php',
'ReleephUserView' => 'applications/releeph/view/user/ReleephUserView.php',
+ 'ShellLogView' => 'applications/harbormaster/view/ShellLogView.php',
'SleepBuildStepImplementation' => 'applications/harbormaster/step/SleepBuildStepImplementation.php',
'SlowvoteEmbedView' => 'applications/slowvote/view/SlowvoteEmbedView.php',
'SlowvoteRemarkupRule' => 'applications/slowvote/remarkup/SlowvoteRemarkupRule.php',
+ 'VariableBuildStepImplementation' => 'applications/harbormaster/step/VariableBuildStepImplementation.php',
),
'function' =>
array(
@@ -2920,9 +2927,15 @@
0 => 'HarbormasterDAO',
1 => 'PhabricatorPolicyInterface',
),
+ 'HarbormasterBuildCancelController' => 'HarbormasterController',
'HarbormasterBuildItem' => 'HarbormasterDAO',
'HarbormasterBuildItemQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
- 'HarbormasterBuildLog' => 'HarbormasterDAO',
+ 'HarbormasterBuildLog' =>
+ array(
+ 0 => 'HarbormasterDAO',
+ 1 => 'PhabricatorPolicyInterface',
+ ),
+ 'HarbormasterBuildLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildPlan' =>
array(
0 => 'HarbormasterDAO',
@@ -2944,6 +2957,7 @@
'HarbormasterBuildStepQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildTarget' => 'HarbormasterDAO',
'HarbormasterBuildTargetQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'HarbormasterBuildViewController' => 'HarbormasterController',
'HarbormasterBuildWorker' => 'PhabricatorWorker',
'HarbormasterBuildable' =>
array(
@@ -2967,6 +2981,7 @@
'HarbormasterObject' => 'HarbormasterDAO',
'HarbormasterPHIDTypeBuild' => 'PhabricatorPHIDType',
'HarbormasterPHIDTypeBuildItem' => 'PhabricatorPHIDType',
+ 'HarbormasterPHIDTypeBuildLog' => 'PhabricatorPHIDType',
'HarbormasterPHIDTypeBuildPlan' => 'PhabricatorPHIDType',
'HarbormasterPHIDTypeBuildStep' => 'PhabricatorPHIDType',
'HarbormasterPHIDTypeBuildTarget' => 'PhabricatorPHIDType',
@@ -3776,6 +3791,7 @@
'PhabricatorGlobalLock' => 'PhutilLock',
'PhabricatorGlobalUploadTargetView' => 'AphrontView',
'PhabricatorHandleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'PhabricatorHarbormasterConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorHashTestCase' => 'PhabricatorTestCase',
'PhabricatorHelpController' => 'PhabricatorController',
'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController',
@@ -4783,8 +4799,10 @@
'ReleephStatusFieldSpecification' => 'ReleephFieldSpecification',
'ReleephSummaryFieldSpecification' => 'ReleephFieldSpecification',
'ReleephUserView' => 'AphrontView',
+ 'ShellLogView' => 'AphrontView',
'SleepBuildStepImplementation' => 'BuildStepImplementation',
'SlowvoteEmbedView' => 'AphrontView',
'SlowvoteRemarkupRule' => 'PhabricatorRemarkupRuleObject',
+ 'VariableBuildStepImplementation' => 'BuildStepImplementation',
),
));
Index: src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php
===================================================================
--- src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php
+++ src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php
@@ -51,6 +51,10 @@
'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterStepEditController',
'delete/(?:(?P<id>\d+)/)?' => 'HarbormasterStepDeleteController',
),
+ 'build/' => array(
+ '(?:(?P<id>\d+)/)?' => 'HarbormasterBuildViewController',
+ 'cancel/(?:(?P<id>\d+)/)?' => 'HarbormasterBuildCancelController',
+ ),
'plan/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?'
=> 'HarbormasterPlanListController',
Index: src/applications/harbormaster/config/PhabricatorHarbormasterConfigOptions.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/config/PhabricatorHarbormasterConfigOptions.php
@@ -0,0 +1,35 @@
+<?php
+
+final class PhabricatorHarbormasterConfigOptions
+ extends PhabricatorApplicationConfigOptions {
+
+ public function getName() {
+ return pht('Harbormaster');
+ }
+
+ public function getDescription() {
+ return pht('Configure Harbormaster build engine.');
+ }
+
+ public function getOptions() {
+ return array(
+ $this->newOption(
+ 'harbormaster.temporary.hosts.whitelist',
+ 'list<string>',
+ array())
+ ->setSummary('Temporary configuration value.')
+ ->setLocked(true)
+ ->setDescription(
+ pht(
+ "This specifies a whitelist of remote hosts that the \"Run ".
+ "Remote Command\" may connect to. This is a temporary ".
+ "configuration option as Drydock is not yet available.".
+ "\n\n".
+ "**This configuration option will be removed in the future and ".
+ "your build configuration will no longer work when Drydock ".
+ "replaces this option. There is ABSOLUTELY NO SUPPORT for ".
+ "using this functionality!**"))
+ );
+ }
+
+}
Index: src/applications/harbormaster/controller/HarbormasterBuildCancelController.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/controller/HarbormasterBuildCancelController.php
@@ -0,0 +1,49 @@
+<?php
+
+final class HarbormasterBuildCancelController
+ extends HarbormasterController {
+
+ private $id;
+
+ public function willProcessRequest(array $data) {
+ $this->id = $data['id'];
+ }
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ $id = $this->id;
+
+ $build = id(new HarbormasterBuildQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($id))
+ ->executeOne();
+ if ($build === null) {
+ return new Aphront404Response();
+ }
+
+ $build_uri = $this->getApplicationURI('/build/'.$build->getID());
+
+ if ($request->isDialogFormPost()) {
+ $build->setCancelRequested(true);
+ $build->save();
+
+ return id(new AphrontRedirectResponse())->setURI($build_uri);
+ }
+
+ $dialog = new AphrontDialogView();
+ $dialog->setTitle(pht('Really cancel build?'))
+ ->setUser($viewer)
+ ->addSubmitButton(pht('Cancel'))
+ ->addCancelButton($build_uri, pht('Don\'t Cancel'));
+ $dialog->appendChild(
+ phutil_tag(
+ 'p',
+ array(),
+ pht(
+ 'Really cancel this build?')));
+ return id(new AphrontDialogResponse())->setDialog($dialog);
+ }
+
+}
Index: src/applications/harbormaster/controller/HarbormasterBuildViewController.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/controller/HarbormasterBuildViewController.php
@@ -0,0 +1,218 @@
+<?php
+
+final class HarbormasterBuildViewController
+ extends HarbormasterController {
+
+ private $id;
+
+ public function willProcessRequest(array $data) {
+ $this->id = $data['id'];
+ }
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ $id = $this->id;
+
+ $build = id(new HarbormasterBuildQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($id))
+ ->executeOne();
+ if (!$build) {
+ return new Aphront404Response();
+ }
+
+ $title = pht("Build %d", $id);
+
+ $header = id(new PHUIHeaderView())
+ ->setHeader($title)
+ ->setUser($viewer)
+ ->setPolicyObject($build);
+
+ $box = id(new PHUIObjectBoxView())
+ ->setHeader($header);
+
+ $actions = $this->buildActionList($build);
+ $this->buildPropertyLists($box, $build, $actions);
+
+ $crumbs = $this->buildApplicationCrumbs();
+ $crumbs->addCrumb(
+ id(new PhabricatorCrumbView())
+ ->setName($title));
+
+ $logs = $this->buildLog($build);
+
+ return $this->buildApplicationPage(
+ array(
+ $crumbs,
+ $box,
+ $logs
+ ),
+ array(
+ 'title' => $title,
+ 'device' => true,
+ ));
+ }
+
+ private function buildLog(HarbormasterBuild $build) {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+ $limit = $request->getInt('l', 25);
+
+ $logs = id(new HarbormasterBuildLogQuery())
+ ->setViewer($viewer)
+ ->withBuildPHIDs(array($build->getPHID()))
+ ->execute();
+
+ $log_boxes = array();
+ foreach ($logs as $log) {
+ $start = 1;
+ $lines = preg_split("/\r\n|\r|\n/", $log->getLogText());
+ if ($limit !== 0) {
+ $start = count($lines) - $limit;
+ if ($start >= 1) {
+ $lines = array_slice($lines, -$limit, $limit);
+ } else {
+ $start = 1;
+ }
+ }
+ $log_view = new ShellLogView();
+ $log_view->setLines($lines);
+ $log_view->setStart($start);
+
+ $header = id(new PHUIHeaderView())
+ ->setHeader(pht(
+ 'Build Log %d (%s - %s)',
+ $log->getID(),
+ $log->getLogSource(),
+ $log->getLogType()))
+ ->setSubheader($this->createLogHeader($build, $log))
+ ->setUser($viewer);
+
+ $log_boxes[] = id(new PHUIObjectBoxView())
+ ->setHeader($header)
+ ->setForm($log_view);
+ }
+
+ return $log_boxes;
+ }
+
+ private function createLogHeader($build, $log) {
+ $request = $this->getRequest();
+ $limit = $request->getInt('l', 25);
+
+ $lines_25 = $this->getApplicationURI('/build/'.$build->getID().'/?l=25');
+ $lines_50 = $this->getApplicationURI('/build/'.$build->getID().'/?l=50');
+ $lines_100 =
+ $this->getApplicationURI('/build/'.$build->getID().'/?l=100');
+ $lines_0 = $this->getApplicationURI('/build/'.$build->getID().'/?l=0');
+
+ $link_25 = phutil_tag('a', array('href' => $lines_25), pht('25'));
+ $link_50 = phutil_tag('a', array('href' => $lines_50), pht('50'));
+ $link_100 = phutil_tag('a', array('href' => $lines_100), pht('100'));
+ $link_0 = phutil_tag('a', array('href' => $lines_0), pht('Unlimited'));
+
+ if ($limit === 25) {
+ $link_25 = phutil_tag('strong', array(), $link_25);
+ } else if ($limit === 50) {
+ $link_50 = phutil_tag('strong', array(), $link_50);
+ } else if ($limit === 100) {
+ $link_100 = phutil_tag('strong', array(), $link_100);
+ } else if ($limit === 0) {
+ $link_0 = phutil_tag('strong', array(), $link_0);
+ }
+
+ return phutil_tag(
+ 'span',
+ array(),
+ array(
+ $link_25,
+ ' - ',
+ $link_50,
+ ' - ',
+ $link_100,
+ ' - ',
+ $link_0,
+ ' Lines'));
+ }
+
+ private function buildActionList(HarbormasterBuild $build) {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+ $id = $build->getID();
+
+ $list = id(new PhabricatorActionListView())
+ ->setUser($viewer)
+ ->setObject($build)
+ ->setObjectURI("/build/{$id}");
+
+ $action =
+ id(new PhabricatorActionView())
+ ->setName(pht('Cancel Build'))
+ ->setIcon('delete');
+ switch ($build->getBuildStatus()) {
+ case HarbormasterBuild::STATUS_PENDING:
+ case HarbormasterBuild::STATUS_WAITING:
+ case HarbormasterBuild::STATUS_BUILDING:
+ $cancel_uri = $this->getApplicationURI('/build/cancel/'.$id.'/');
+ $action
+ ->setHref($cancel_uri)
+ ->setWorkflow(true);
+ break;
+ default:
+ $action
+ ->setDisabled(true);
+ break;
+ }
+ $list->addAction($action);
+
+ return $list;
+ }
+
+ private function buildPropertyLists(
+ PHUIObjectBoxView $box,
+ HarbormasterBuild $build,
+ PhabricatorActionListView $actions) {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ $properties = id(new PHUIPropertyListView())
+ ->setUser($viewer)
+ ->setObject($build)
+ ->setActionList($actions);
+ $box->addPropertyList($properties);
+
+ $properties->addProperty(
+ pht('Status'),
+ $this->getStatus($build));
+
+ }
+
+ private function getStatus(HarbormasterBuild $build) {
+ if ($build->getCancelRequested()) {
+ return pht('Cancelling');
+ }
+ switch ($build->getBuildStatus()) {
+ case HarbormasterBuild::STATUS_INACTIVE:
+ return pht('Inactive');
+ case HarbormasterBuild::STATUS_PENDING:
+ return pht('Pending');
+ case HarbormasterBuild::STATUS_WAITING:
+ return pht('Waiting on Resource');
+ case HarbormasterBuild::STATUS_BUILDING:
+ return pht('Building');
+ case HarbormasterBuild::STATUS_PASSED:
+ return pht('Passed');
+ case HarbormasterBuild::STATUS_FAILED:
+ return pht('Failed');
+ case HarbormasterBuild::STATUS_ERROR:
+ return pht('Unexpected Error');
+ case HarbormasterBuild::STATUS_CANCELLED:
+ return pht('Cancelled');
+ default:
+ return pht('Unknown');
+ }
+ }
+
+}
Index: src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
===================================================================
--- src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
+++ src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
@@ -34,38 +34,49 @@
$build_list = id(new PHUIObjectItemListView())
->setUser($viewer);
foreach ($builds as $build) {
+ $view_uri = $this->getApplicationURI('/build/'.$build->getID().'/');
$item = id(new PHUIObjectItemView())
->setObjectName(pht('Build %d', $build->getID()))
- ->setHeader($build->getName());
- switch ($build->getBuildStatus()) {
- case HarbormasterBuild::STATUS_INACTIVE:
- $item->setBarColor('grey');
- $item->addAttribute(pht('Inactive'));
- break;
- case HarbormasterBuild::STATUS_PENDING:
- $item->setBarColor('blue');
- $item->addAttribute(pht('Pending'));
- break;
- case HarbormasterBuild::STATUS_WAITING:
- $item->setBarColor('blue');
- $item->addAttribute(pht('Waiting on Resource'));
- break;
- case HarbormasterBuild::STATUS_BUILDING:
- $item->setBarColor('yellow');
- $item->addAttribute(pht('Building'));
- break;
- case HarbormasterBuild::STATUS_PASSED:
- $item->setBarColor('green');
- $item->addAttribute(pht('Passed'));
- break;
- case HarbormasterBuild::STATUS_FAILED:
- $item->setBarColor('red');
- $item->addAttribute(pht('Failed'));
- break;
- case HarbormasterBuild::STATUS_ERROR:
- $item->setBarColor('red');
- $item->addAttribute(pht('Unexpected Error'));
- break;
+ ->setHeader($build->getName())
+ ->setHref($view_uri);
+ if ($build->getCancelRequested()) {
+ $item->setBarColor('black');
+ $item->addAttribute(pht('Cancelling'));
+ } else {
+ switch ($build->getBuildStatus()) {
+ case HarbormasterBuild::STATUS_INACTIVE:
+ $item->setBarColor('grey');
+ $item->addAttribute(pht('Inactive'));
+ break;
+ case HarbormasterBuild::STATUS_PENDING:
+ $item->setBarColor('blue');
+ $item->addAttribute(pht('Pending'));
+ break;
+ case HarbormasterBuild::STATUS_WAITING:
+ $item->setBarColor('blue');
+ $item->addAttribute(pht('Waiting on Resource'));
+ break;
+ case HarbormasterBuild::STATUS_BUILDING:
+ $item->setBarColor('yellow');
+ $item->addAttribute(pht('Building'));
+ break;
+ case HarbormasterBuild::STATUS_PASSED:
+ $item->setBarColor('green');
+ $item->addAttribute(pht('Passed'));
+ break;
+ case HarbormasterBuild::STATUS_FAILED:
+ $item->setBarColor('red');
+ $item->addAttribute(pht('Failed'));
+ break;
+ case HarbormasterBuild::STATUS_ERROR:
+ $item->setBarColor('red');
+ $item->addAttribute(pht('Unexpected Error'));
+ break;
+ case HarbormasterBuild::STATUS_CANCELLED:
+ $item->setBarColor('black');
+ $item->addAttribute(pht('Cancelled'));
+ break;
+ }
}
$build_list->addItem($item);
}
Index: src/applications/harbormaster/controller/HarbormasterStepEditController.php
===================================================================
--- src/applications/harbormaster/controller/HarbormasterStepEditController.php
+++ src/applications/harbormaster/controller/HarbormasterStepEditController.php
@@ -63,6 +63,11 @@
$form = id(new AphrontFormView())
->setUser($viewer);
+ $instructions = $implementation->getSettingRemarkupInstructions();
+ if ($instructions !== null) {
+ $form->appendRemarkupInstructions($instructions);
+ }
+
// We need to render out all of the fields for the settings that
// the implementation has.
foreach ($implementation->getSettingDefinitions() as $name => $opt) {
Index: src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildLog.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildLog.php
@@ -0,0 +1,37 @@
+<?php
+
+final class HarbormasterPHIDTypeBuildLog extends PhabricatorPHIDType {
+
+ const TYPECONST = 'HMCL';
+
+ public function getTypeConstant() {
+ return self::TYPECONST;
+ }
+
+ public function getTypeName() {
+ return pht('Build Log');
+ }
+
+ public function newObject() {
+ return new HarbormasterBuildLog();
+ }
+
+ protected function buildQueryForObjects(
+ PhabricatorObjectQuery $query,
+ array $phids) {
+
+ return id(new HarbormasterBuildLogQuery())
+ ->withPHIDs($phids);
+ }
+
+ public function loadHandles(
+ PhabricatorHandleQuery $query,
+ array $handles,
+ array $objects) {
+
+ foreach ($handles as $phid => $handle) {
+ $build_log = $objects[$phid];
+ }
+ }
+
+}
Index: src/applications/harbormaster/query/HarbormasterBuildLogQuery.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/query/HarbormasterBuildLogQuery.php
@@ -0,0 +1,98 @@
+<?php
+
+final class HarbormasterBuildLogQuery
+ extends PhabricatorCursorPagedPolicyAwareQuery {
+
+ private $ids;
+ private $phids;
+ private $buildPHIDs;
+
+ public function withIDs(array $ids) {
+ $this->ids = $ids;
+ return $this;
+ }
+
+ public function withPHIDs(array $phids) {
+ $this->phids = $phids;
+ return $this;
+ }
+
+ public function withBuildPHIDs(array $build_phids) {
+ $this->buildPHIDs = $build_phids;
+ return $this;
+ }
+
+ protected function loadPage() {
+ $table = new HarbormasterBuildLog();
+ $conn_r = $table->establishConnection('r');
+
+ $data = queryfx_all(
+ $conn_r,
+ 'SELECT * FROM %T %Q %Q %Q',
+ $table->getTableName(),
+ $this->buildWhereClause($conn_r),
+ $this->buildOrderClause($conn_r),
+ $this->buildLimitClause($conn_r));
+
+ return $table->loadAllFromArray($data);
+ }
+
+ protected function willFilterPage(array $page) {
+ $builds = array();
+
+ $build_phids = array_filter(mpull($page, 'getBuildPHID'));
+ if ($build_phids) {
+ $builds = id(new HarbormasterBuildQuery())
+ ->setViewer($this->getViewer())
+ ->withPHIDs($build_phids)
+ ->setParentQuery($this)
+ ->execute();
+ $builds = mpull($builds, null, 'getPHID');
+ }
+
+ foreach ($page as $key => $build_log) {
+ $build_phid = $build_log->getBuildPHID();
+ if (empty($builds[$build_phid])) {
+ unset($page[$key]);
+ continue;
+ }
+ $build_log->attachBuild($builds[$build_phid]);
+ }
+
+ return $page;
+ }
+
+ private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
+ $where = array();
+
+ if ($this->ids) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'id IN (%Ld)',
+ $this->ids);
+ }
+
+ if ($this->phids) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'phid IN (%Ls)',
+ $this->phids);
+ }
+
+ if ($this->buildPHIDs) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'buildPHID IN (%Ls)',
+ $this->buildPHIDs);
+ }
+
+ $where[] = $this->buildPagingClause($conn_r);
+
+ return $this->formatWhereClause($where);
+ }
+
+ public function getQueryApplicationClass() {
+ return 'PhabricatorApplicationHarbormaster';
+ }
+
+}
Index: src/applications/harbormaster/step/BuildStepImplementation.php
===================================================================
--- src/applications/harbormaster/step/BuildStepImplementation.php
+++ src/applications/harbormaster/step/BuildStepImplementation.php
@@ -38,7 +38,9 @@
/**
* Run the build step against the specified build.
*/
- abstract public function execute(HarbormasterBuild $build);
+ abstract public function execute(
+ HarbormasterBuild $build,
+ HarbormasterBuildStep $build_step);
/**
* Gets the settings for this build step.
@@ -85,4 +87,11 @@
public function getSettingDefinitions() {
return array();
}
+
+ /**
+ * Return relevant setting instructions as Remarkup.
+ */
+ public function getSettingRemarkupInstructions() {
+ return null;
+ }
}
Index: src/applications/harbormaster/step/SleepBuildStepImplementation.php
===================================================================
--- src/applications/harbormaster/step/SleepBuildStepImplementation.php
+++ src/applications/harbormaster/step/SleepBuildStepImplementation.php
@@ -16,7 +16,10 @@
return pht('Sleep for %s seconds.', $settings['seconds']);
}
- public function execute(HarbormasterBuild $build) {
+ public function execute(
+ HarbormasterBuild $build,
+ HarbormasterBuildStep $build_step) {
+
$settings = $this->getSettings();
sleep($settings['seconds']);
Index: src/applications/harbormaster/step/VariableBuildStepImplementation.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/step/VariableBuildStepImplementation.php
@@ -0,0 +1,67 @@
+<?php
+
+abstract class VariableBuildStepImplementation extends BuildStepImplementation {
+
+ public function retrieveVariablesFromBuild(HarbormasterBuild $build) {
+ $results = array(
+ 'revision' => null,
+ 'commit' => null,
+ 'repository' => null,
+ 'vcs' => null,
+ 'uri' => null,
+ 'timestamp' => null);
+
+ $buildable = $build->getBuildable();
+ $object = $buildable->getBuildableObject();
+
+ $repo = null;
+ if ($object instanceof DifferentialRevision) {
+ $results['revision'] = $object->getID();
+ $repo = $object->getRepository();
+ } else if ($object instanceof PhabricatorRepositoryCommit) {
+ $results['commit'] = $object->getCommitIdentifier();
+ $repo = $object->getRepository();
+ }
+
+ $results['repository'] = $repo->getCallsign();
+ $results['vcs'] = $repo->getVersionControlSystem();
+ $results['uri'] = $repo->getPublicRemoteURI();
+ $results['timestamp'] = time();
+
+ return $results;
+ }
+
+ public function mergeVariables(HarbormasterBuild $build, $string) {
+ $variables = $this->retrieveVariablesFromBuild($build);
+ foreach ($variables as $name => $value) {
+ if ($value === null) {
+ $value = '';
+ }
+ $string = str_replace('${'.$name.'}', $value, $string);
+ }
+ return $string;
+ }
+
+ public function getAvailableVariables() {
+ return array(
+ 'revision' => pht('The differential revision ID, if applicable.'),
+ 'commit' => pht('The commit identifier, if applicable.'),
+ 'repository' => pht('The callsign of the repository in Phabricator.'),
+ 'vcs' => pht('The version control system, either "svn", "hg" or "git".'),
+ 'uri' => pht('The URI to clone or checkout the repository from.'),
+ 'timestamp' => pht('The current UNIX timestamp.'));
+ }
+
+ public function getSettingRemarkupInstructions() {
+ $text = '';
+ $text .= pht('The following variables are available: ')."\n";
+ $text .= "\n";
+ foreach ($this->getAvailableVariables() as $name => $desc) {
+ $text .= ' - `'.$name.'`: '.$desc."\n";
+ }
+ $text .= "\n";
+ $text .= "Use `\${name}` to merge a variable into a setting.";
+ return $text;
+ }
+
+}
Index: src/applications/harbormaster/storage/build/HarbormasterBuild.php
===================================================================
--- src/applications/harbormaster/storage/build/HarbormasterBuild.php
+++ src/applications/harbormaster/storage/build/HarbormasterBuild.php
@@ -6,6 +6,7 @@
protected $buildablePHID;
protected $buildPlanPHID;
protected $buildStatus;
+ protected $cancelRequested;
private $buildable = self::ATTACHABLE;
private $buildPlan = self::ATTACHABLE;
@@ -45,9 +46,15 @@
*/
const STATUS_ERROR = 'error';
+ /**
+ * The build has been cancelled.
+ */
+ const STATUS_CANCELLED = 'cancelled';
+
public static function initializeNewBuild(PhabricatorUser $actor) {
return id(new HarbormasterBuild())
- ->setBuildStatus(self::STATUS_INACTIVE);
+ ->setBuildStatus(self::STATUS_INACTIVE)
+ ->setCancelRequested(false);
}
public function getConfiguration() {
@@ -87,6 +94,37 @@
return $this->assertAttached($this->buildPlan);
}
+ public function createLog(
+ HarbormasterBuildStep $build_step,
+ $log_source,
+ $log_type) {
+
+ $log = HarbormasterBuildLog::initializeNewBuildLog($this, $build_step);
+ $log->setLogSource($log_source);
+ $log->setLogType($log_type);
+ $log->save();
+ return $log;
+ }
+
+ /**
+ * Checks for and handles build cancellation. If this method returns
+ * true, the caller should stop any current operations and return control
+ * as quickly as possible.
+ */
+ public function checkForCancellation() {
+ // Here we load a copy of the current build and check whether
+ // the user requested cancellation. We can't do `reload()` here
+ // in case there are changes that have not yet been saved.
+ $copy = id(new HarbormasterBuild())->load($this->getID());
+ if ($copy->getCancelRequested()) {
+ $this->setBuildStatus(HarbormasterBuild::STATUS_CANCELLED);
+ $this->setCancelRequested(false);
+ $this->save();
+ return true;
+ }
+ return false;
+ }
+
/* -( PhabricatorPolicyInterface )----------------------------------------- */
Index: src/applications/harbormaster/storage/build/HarbormasterBuildLog.php
===================================================================
--- src/applications/harbormaster/storage/build/HarbormasterBuildLog.php
+++ src/applications/harbormaster/storage/build/HarbormasterBuildLog.php
@@ -1,7 +1,206 @@
<?php
-final class HarbormasterBuildLog extends HarbormasterDAO {
+final class HarbormasterBuildLog extends HarbormasterDAO
+ implements PhabricatorPolicyInterface {
+
+ protected $buildPHID;
+ protected $buildStepPHID;
+ protected $logSource;
+ protected $logType;
+ protected $duration;
+ protected $live;
+
+ private $build = self::ATTACHABLE;
+ private $buildStep = self::ATTACHABLE;
+
+ const CHUNK_BYTE_LIMIT = 102400;
+
+ /**
+ * The log is encoded as plain text.
+ */
+ const ENCODING_TEXT = 'text';
+
+ public static function initializeNewBuildLog(
+ HarbormasterBuild $build,
+ HarbormasterBuildStep $build_step) {
+
+ return id(new HarbormasterBuildLog())
+ ->setBuildPHID($build->getPHID())
+ ->setBuildStepPHID($build_step->getPHID())
+ ->setDuration(null)
+ ->setLive(false);
+ }
+
+ public function getConfiguration() {
+ return array(
+ self::CONFIG_AUX_PHID => true,
+ ) + parent::getConfiguration();
+ }
+
+ public function generatePHID() {
+ return PhabricatorPHID::generateNewPHID(
+ HarbormasterPHIDTypeBuildLog::TYPECONST);
+ }
+
+ public function attachBuild(HarbormasterBuild $build) {
+ $this->build = $build;
+ return $this;
+ }
+
+ public function getBuild() {
+ return $this->assertAttached($this->build);
+ }
+
+ public function getName() {
+ return pht('Build Log');
+ }
+
+ public function attachBuildStep(
+ HarbormasterBuildStep $build_step = null) {
+ $this->buildStep = $build_step;
+ return $this;
+ }
+
+ public function getBuildStep() {
+ return $this->assertAttached($this->buildStep);
+ }
+
+ public function start() {
+ if ($this->getLive()) {
+ throw new Exception("Live logging has already started for this log.");
+ }
+
+ $this->setLive(true);
+ $this->save();
+
+ return time();
+ }
+
+ public function append($content) {
+ if (!$this->getLive()) {
+ throw new Exception("Start logging before appending data to the log.");
+ }
+ if (strlen($content) === 0) {
+ return;
+ }
+
+ // If the length of the content is greater than the chunk size limit,
+ // then we can never fit the content in a single record. We need to
+ // split our content out and call append on it for as many parts as there
+ // are to the content.
+ if (strlen($content) > self::CHUNK_BYTE_LIMIT) {
+ $current = $content;
+ while (strlen($current) > self::CHUNK_BYTE_LIMIT) {
+ $part = substr($current, 0, self::CHUNK_BYTE_LIMIT);
+ $current = substr($current, self::CHUNK_BYTE_LIMIT);
+ $this->append($part);
+ }
+ return;
+ }
+
+ // Retrieve the size of last chunk from the DB for this log. If the
+ // chunk is over 500K, then we need to create a new log entry.
+ $conn = $this->establishConnection('w');
+ $result = queryfx_all(
+ $conn,
+ 'SELECT id, size, encoding '.
+ 'FROM harbormaster_buildlogchunk '.
+ 'WHERE logID = %d '.
+ 'ORDER BY id DESC '.
+ 'LIMIT 1',
+ $this->getID());
+ if (count($result) === 0 ||
+ $result[0]["size"] + strlen($content) > self::CHUNK_BYTE_LIMIT ||
+ $result[0]["encoding"] !== self::ENCODING_TEXT) {
+
+ // We must insert a new chunk because the data we are appending
+ // won't fit into the existing one, or we don't have any existing
+ // chunk data.
+ queryfx(
+ $conn,
+ 'INSERT INTO harbormaster_buildlogchunk '.
+ '(logID, encoding, size, chunk) '.
+ 'VALUES '.
+ '(%d, %s, %d, %s)',
+ $this->getID(),
+ self::ENCODING_TEXT,
+ strlen($content),
+ $content);
+ } else {
+ // We have a resulting record that we can append our content onto.
+ queryfx(
+ $conn,
+ 'UPDATE harbormaster_buildlogchunk '.
+ 'SET chunk = CONCAT(chunk, %s), size = LENGTH(CONCAT(chunk, %s))'.
+ 'WHERE id = %d',
+ $content,
+ $content,
+ $result[0]["id"]);
+ }
+ }
+
+ public function finalize($start = 0) {
+ if (!$this->getLive()) {
+ throw new Exception("Start logging before finalizing it.");
+ }
+
+ // TODO: Encode the log contents in a gzipped format.
+ $this->reload();
+ if ($start > 0) {
+ $this->setDuration(time() - $start);
+ }
+ $this->setLive(false);
+ $this->save();
+ }
+
+ public function getLogText() {
+ // TODO: This won't cope very well if we're pulling like a 700MB
+ // log file out of the DB. We should probably implement some sort
+ // of optional limit parameter so that when we're rendering out only
+ // 25 lines in the UI, we don't wastefully read in the whole log.
+
+ // We have to read our content out of the database and stitch all of
+ // the log data back together.
+ $conn = $this->establishConnection('r');
+ $result = queryfx_all(
+ $conn,
+ 'SELECT chunk '.
+ 'FROM harbormaster_buildlogchunk '.
+ 'WHERE logID = %d '.
+ 'ORDER BY id ASC',
+ $this->getID());
+
+ $content = "";
+ foreach ($result as $row) {
+ $content .= $row["chunk"];
+ }
+ return $content;
+ }
+
+
+/* -( PhabricatorPolicyInterface )----------------------------------------- */
+
+
+ public function getCapabilities() {
+ return array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ );
+ }
+
+ public function getPolicy($capability) {
+ return $this->getBuild()->getPolicy($capability);
+ }
+
+ public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+ return $this->getBuild()->hasAutomaticCapability(
+ $capability,
+ $viewer);
+ }
+
+ public function describeAutomaticCapability($capability) {
+ return pht(
+ 'Users must be able to see a build to view it\'s build log.');
+ }
- protected $buildItemPHID;
}
Index: src/applications/harbormaster/view/ShellLogView.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/view/ShellLogView.php
@@ -0,0 +1,101 @@
+<?php
+
+final class ShellLogView extends AphrontView {
+
+ private $start = 1;
+ private $lines;
+ private $limit;
+ private $highlights = array();
+
+ public function setStart($start) {
+ $this->start = $start;
+ return $this;
+ }
+
+ public function setLimit($limit) {
+ $this->limit = $limit;
+ return $this;
+ }
+
+ public function setLines(array $lines) {
+ $this->lines = $lines;
+ return $this;
+ }
+
+ public function setHighlights(array $highlights) {
+ $this->highlights = array_fuse($highlights);
+ return $this;
+ }
+
+ public function render() {
+ require_celerity_resource('phabricator-source-code-view-css');
+ require_celerity_resource('syntax-highlighting-css');
+
+ Javelin::initBehavior('phabricator-oncopy', array());
+
+ $line_number = $this->start;
+
+ $rows = array();
+ foreach ($this->lines as $line) {
+ $hit_limit = $this->limit &&
+ ($line_number == $this->limit) &&
+ (count($this->lines) != $this->limit);
+
+ if ($hit_limit) {
+ $content_number = '';
+ $content_line = phutil_tag(
+ 'span',
+ array(
+ 'class' => 'c',
+ ),
+ pht('...'));
+ } else {
+ $content_number = $line_number;
+ $content_line = $line;
+ }
+
+ $row_attributes = array();
+ if (isset($this->highlights[$line_number])) {
+ $row_attributes['class'] = 'phabricator-source-highlight';
+ }
+
+ // TODO: Provide nice links.
+
+ $rows[] = phutil_tag(
+ 'tr',
+ $row_attributes,
+ hsprintf(
+ '<th class="phabricator-source-line" '.
+ 'style="background-color: #fff;">%s</th>'.
+ '<td class="phabricator-source-code">%s</td>',
+ $content_number,
+ $content_line));
+
+ if ($hit_limit) {
+ break;
+ }
+
+ $line_number++;
+ }
+
+ $classes = array();
+ $classes[] = 'phabricator-source-code-view';
+ $classes[] = 'remarkup-code';
+ $classes[] = 'PhabricatorMonospaced';
+
+ return phutil_tag(
+ 'div',
+ array(
+ 'class' => 'phabricator-source-code-container',
+ 'style' => 'background-color: black; color: white;'
+ ),
+ phutil_tag(
+ 'table',
+ array(
+ 'class' => implode(' ', $classes),
+ 'style' => 'background-color: black'
+ ),
+ phutil_implode_html('', $rows)));
+ }
+
+}
Index: src/applications/harbormaster/worker/HarbormasterBuildWorker.php
===================================================================
--- src/applications/harbormaster/worker/HarbormasterBuildWorker.php
+++ src/applications/harbormaster/worker/HarbormasterBuildWorker.php
@@ -25,6 +25,13 @@
pht('Invalid build ID "%s".', $id));
}
+ // It's possible for the user to request cancellation before
+ // a worker picks up a build. We check to see if the build
+ // is already cancelled, and return if it is.
+ if ($build->checkForCancellation()) {
+ return;
+ }
+
try {
$build->setBuildStatus(HarbormasterBuild::STATUS_BUILDING);
$build->save();
@@ -44,12 +51,21 @@
$build->setBuildStatus(HarbormasterBuild::STATUS_ERROR);
break;
}
- $implementation->execute($build);
+ $implementation->execute($build, $step);
if ($build->getBuildStatus() !== HarbormasterBuild::STATUS_BUILDING) {
break;
}
+ if ($build->checkForCancellation()) {
+ break;
+ }
}
+ // Check to see if the user requested cancellation. If they did and
+ // we get to here, they might have either cancelled too late, or the
+ // step isn't cancellation aware. In either case we ignore the result
+ // and move to a cancelled state.
+ $build->checkForCancellation();
+
// If we get to here, then the build has finished. Set it to passed
// if no build step explicitly set the status.
if ($build->getBuildStatus() === HarbormasterBuild::STATUS_BUILDING) {
Index: src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
===================================================================
--- src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
+++ src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
@@ -1744,6 +1744,10 @@
'type' => 'sql',
'name' => $this->getPatchPath('20131106.nuance-v0.sql'),
),
+ '20131107.buildlog.sql' => array(
+ 'type' => 'sql',
+ 'name' => $this->getPatchPath('20131107.buildlog.sql'),
+ ),
);
}
}

File Metadata

Mime Type
text/plain
Expires
Wed, Dec 25, 8:53 PM (4 h, 28 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6924886
Default Alt Text
D7521.id17007.diff (50 KB)

Event Timeline