mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-09 16:32:39 +01:00
Implemented support for build logs
Summary: Depends on D7519. This implements support for build logs in Harbormaster. This includes support for appending to a log from the "Run Remote Command" build step. It also adds the ability to cancel builds. Currently the build view page doesn't update the logs live; I'm sure this can be achieved with Javelin, but I don't have enough experience with Javelin to actually make it poll from updates to content in the background. {F79151} {F79153} {F79150} {F79152} Test Plan: Tested this by setting up SSH on a Windows machine and using a Remote Command configured with: ``` C:\Windows\system32\cmd.exe /C cd C:\Build && mkdir Build_${timestamp} && cd Build_${timestamp} && git clone --recursive https://github.com/hach-que/Tychaia.git && cd Tychaia && Protobuild.exe && C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe Tychaia.Windows.sln ``` and observed the output of the build stream from the Windows machine into Phabricator. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley CC: Korvin, epriestley, aran Maniphest Tasks: T1049 Differential Revision: https://secure.phabricator.com/D7521
This commit is contained in:
parent
d0de4dab24
commit
0ac1be7094
19 changed files with 1031 additions and 93 deletions
26
resources/sql/patches/20131107.buildlog.sql
Normal file
26
resources/sql/patches/20131107.buildlog.sql
Normal file
|
@ -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;
|
|
@ -569,22 +569,22 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'/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 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'/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 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'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 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'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 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'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 @@ celerity_register_resource_map(array(
|
|||
), array(
|
||||
'packages' =>
|
||||
array(
|
||||
'f350af41' =>
|
||||
'f0d63822' =>
|
||||
array(
|
||||
'name' => 'core.pkg.css',
|
||||
'symbols' =>
|
||||
|
@ -4377,7 +4377,7 @@ celerity_register_resource_map(array(
|
|||
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 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'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 @@ celerity_register_resource_map(array(
|
|||
'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 @@ celerity_register_resource_map(array(
|
|||
'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',
|
||||
),
|
||||
));
|
||||
|
|
|
@ -652,9 +652,11 @@ phutil_register_library_map(array(
|
|||
'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 @@ phutil_register_library_map(array(
|
|||
'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 @@ phutil_register_library_map(array(
|
|||
'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 @@ phutil_register_library_map(array(
|
|||
'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 @@ phutil_register_library_map(array(
|
|||
'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 @@ phutil_register_library_map(array(
|
|||
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 @@ phutil_register_library_map(array(
|
|||
'HarbormasterBuildStepQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'HarbormasterBuildTarget' => 'HarbormasterDAO',
|
||||
'HarbormasterBuildTargetQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'HarbormasterBuildViewController' => 'HarbormasterController',
|
||||
'HarbormasterBuildWorker' => 'PhabricatorWorker',
|
||||
'HarbormasterBuildable' =>
|
||||
array(
|
||||
|
@ -2967,6 +2981,7 @@ phutil_register_library_map(array(
|
|||
'HarbormasterObject' => 'HarbormasterDAO',
|
||||
'HarbormasterPHIDTypeBuild' => 'PhabricatorPHIDType',
|
||||
'HarbormasterPHIDTypeBuildItem' => 'PhabricatorPHIDType',
|
||||
'HarbormasterPHIDTypeBuildLog' => 'PhabricatorPHIDType',
|
||||
'HarbormasterPHIDTypeBuildPlan' => 'PhabricatorPHIDType',
|
||||
'HarbormasterPHIDTypeBuildStep' => 'PhabricatorPHIDType',
|
||||
'HarbormasterPHIDTypeBuildTarget' => 'PhabricatorPHIDType',
|
||||
|
@ -3776,6 +3791,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorGlobalLock' => 'PhutilLock',
|
||||
'PhabricatorGlobalUploadTargetView' => 'AphrontView',
|
||||
'PhabricatorHandleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorHarbormasterConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
'PhabricatorHashTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorHelpController' => 'PhabricatorController',
|
||||
'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController',
|
||||
|
@ -4783,8 +4799,10 @@ phutil_register_library_map(array(
|
|||
'ReleephStatusFieldSpecification' => 'ReleephFieldSpecification',
|
||||
'ReleephSummaryFieldSpecification' => 'ReleephFieldSpecification',
|
||||
'ReleephUserView' => 'AphrontView',
|
||||
'ShellLogView' => 'AphrontView',
|
||||
'SleepBuildStepImplementation' => 'BuildStepImplementation',
|
||||
'SlowvoteEmbedView' => 'AphrontView',
|
||||
'SlowvoteRemarkupRule' => 'PhabricatorRemarkupRuleObject',
|
||||
'VariableBuildStepImplementation' => 'BuildStepImplementation',
|
||||
),
|
||||
));
|
||||
|
|
|
@ -51,6 +51,10 @@ final class PhabricatorApplicationHarbormaster extends PhabricatorApplication {
|
|||
'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',
|
||||
|
|
|
@ -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!**"))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -34,38 +34,49 @@ final class HarbormasterBuildableViewController
|
|||
$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);
|
||||
}
|
||||
|
|
|
@ -63,6 +63,11 @@ final class HarbormasterStepEditController
|
|||
$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) {
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
|
||||
}
|
|
@ -38,7 +38,9 @@ abstract class BuildStepImplementation {
|
|||
/**
|
||||
* 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 @@ abstract class BuildStepImplementation {
|
|||
public function getSettingDefinitions() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return relevant setting instructions as Remarkup.
|
||||
*/
|
||||
public function getSettingRemarkupInstructions() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,10 @@ final class SleepBuildStepImplementation extends BuildStepImplementation {
|
|||
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']);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -6,6 +6,7 @@ final class HarbormasterBuild extends HarbormasterDAO
|
|||
protected $buildablePHID;
|
||||
protected $buildPlanPHID;
|
||||
protected $buildStatus;
|
||||
protected $cancelRequested;
|
||||
|
||||
private $buildable = self::ATTACHABLE;
|
||||
private $buildPlan = self::ATTACHABLE;
|
||||
|
@ -45,9 +46,15 @@ final class HarbormasterBuild extends HarbormasterDAO
|
|||
*/
|
||||
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 @@ final class HarbormasterBuild extends HarbormasterDAO
|
|||
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 )----------------------------------------- */
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
101
src/applications/harbormaster/view/ShellLogView.php
Normal file
101
src/applications/harbormaster/view/ShellLogView.php
Normal file
|
@ -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)));
|
||||
}
|
||||
|
||||
}
|
|
@ -25,6 +25,13 @@ final class HarbormasterBuildWorker extends PhabricatorWorker {
|
|||
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 @@ final class HarbormasterBuildWorker extends PhabricatorWorker {
|
|||
$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) {
|
||||
|
|
|
@ -1744,6 +1744,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
|
|||
'type' => 'sql',
|
||||
'name' => $this->getPatchPath('20131106.nuance-v0.sql'),
|
||||
),
|
||||
'20131107.buildlog.sql' => array(
|
||||
'type' => 'sql',
|
||||
'name' => $this->getPatchPath('20131107.buildlog.sql'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue