Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14416242
D7521.id17007.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
50 KB
Referenced Files
None
Subscribers
None
D7521.id17007.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D7521: Implemented support for build logs
Attached
Detach File
Event Timeline
Log In to Comment