@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

authored by

James Rhodes and committed by
epriestley
0ac1be70 d0de4dab

+1031 -93
+26
resources/sql/patches/20131107.buildlog.sql
··· 1 + CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildlog ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, 4 + buildPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 5 + buildStepPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 6 + logSource VARCHAR(255) NULL COLLATE utf8_bin, 7 + logType VARCHAR(255) NULL COLLATE utf8_bin, 8 + duration INT UNSIGNED NULL, 9 + live BOOLEAN NOT NULL, 10 + dateCreated INT UNSIGNED NOT NULL, 11 + dateModified INT UNSIGNED NOT NULL, 12 + KEY `key_build` (buildPHID, buildStepPHID), 13 + UNIQUE KEY `key_phid` (phid) 14 + ) ENGINE=InnoDB, COLLATE utf8_general_ci; 15 + 16 + ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_build 17 + ADD COLUMN cancelRequested BOOLEAN NOT NULL; 18 + 19 + CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildlogchunk ( 20 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 21 + logID INT UNSIGNED NOT NULL COLLATE utf8_bin, 22 + encoding VARCHAR(30) NOT NULL COLLATE utf8_bin, 23 + size LONG NULL, 24 + chunk LONGBLOB NOT NULL, 25 + KEY `key_log` (logID) 26 + ) ENGINE=InnoDB, COLLATE utf8_general_ci;
+56 -56
src/__celerity_resource_map__.php
··· 569 569 ), 570 570 '/rsrc/image/sprite-apps-X2.png' => 571 571 array( 572 - 'hash' => '68bbb3f409d0eb42d65dd94769813044', 573 - 'uri' => '/res/68bbb3f4/rsrc/image/sprite-apps-X2.png', 572 + 'hash' => '67e8a6bf2d7fbb0b7961f1a0dcf8592b', 573 + 'uri' => '/res/67e8a6bf/rsrc/image/sprite-apps-X2.png', 574 574 'disk' => '/rsrc/image/sprite-apps-X2.png', 575 575 'type' => 'png', 576 576 ), 577 577 '/rsrc/image/sprite-apps-large-X2.png' => 578 578 array( 579 - 'hash' => '15368afbac0e1402c20f99f3166cdb11', 580 - 'uri' => '/res/15368afb/rsrc/image/sprite-apps-large-X2.png', 579 + 'hash' => '9bed1778022e2bd25d658842be54844d', 580 + 'uri' => '/res/9bed1778/rsrc/image/sprite-apps-large-X2.png', 581 581 'disk' => '/rsrc/image/sprite-apps-large-X2.png', 582 582 'type' => 'png', 583 583 ), 584 584 '/rsrc/image/sprite-apps-large.png' => 585 585 array( 586 - 'hash' => 'b1f1de55803cf22eb3beb391fff17b04', 587 - 'uri' => '/res/b1f1de55/rsrc/image/sprite-apps-large.png', 586 + 'hash' => '518d6e5487c8d3758921ad85c1bb7d60', 587 + 'uri' => '/res/518d6e54/rsrc/image/sprite-apps-large.png', 588 588 'disk' => '/rsrc/image/sprite-apps-large.png', 589 589 'type' => 'png', 590 590 ), ··· 597 597 ), 598 598 '/rsrc/image/sprite-apps.png' => 599 599 array( 600 - 'hash' => 'bf7feaae848d44a461e63123c28e402f', 601 - 'uri' => '/res/bf7feaae/rsrc/image/sprite-apps.png', 600 + 'hash' => '9024ab95247f936f41c0b51c75e2e228', 601 + 'uri' => '/res/9024ab95/rsrc/image/sprite-apps.png', 602 602 'disk' => '/rsrc/image/sprite-apps.png', 603 603 'type' => 'png', 604 604 ), ··· 1197 1197 ), 1198 1198 'herald-rule-editor' => 1199 1199 array( 1200 - 'uri' => '/res/a561eb19/rsrc/js/application/herald/HeraldRuleEditor.js', 1200 + 'uri' => '/res/928275b4/rsrc/js/application/herald/HeraldRuleEditor.js', 1201 1201 'type' => 'js', 1202 1202 'requires' => 1203 1203 array( ··· 4183 4183 ), 4184 4184 'sprite-apps-css' => 4185 4185 array( 4186 - 'uri' => '/res/37c55e75/rsrc/css/sprite-apps.css', 4186 + 'uri' => '/res/774f4bad/rsrc/css/sprite-apps.css', 4187 4187 'type' => 'css', 4188 4188 'requires' => 4189 4189 array( ··· 4192 4192 ), 4193 4193 'sprite-apps-large-css' => 4194 4194 array( 4195 - 'uri' => '/res/8ddded36/rsrc/css/sprite-apps-large.css', 4195 + 'uri' => '/res/b547fab1/rsrc/css/sprite-apps-large.css', 4196 4196 'type' => 'css', 4197 4197 'requires' => 4198 4198 array( ··· 4328 4328 ), array( 4329 4329 'packages' => 4330 4330 array( 4331 - 'f350af41' => 4331 + 'f0d63822' => 4332 4332 array( 4333 4333 'name' => 'core.pkg.css', 4334 4334 'symbols' => ··· 4377 4377 41 => 'phabricator-tag-view-css', 4378 4378 42 => 'phui-list-view-css', 4379 4379 ), 4380 - 'uri' => '/res/pkg/f350af41/core.pkg.css', 4380 + 'uri' => '/res/pkg/f0d63822/core.pkg.css', 4381 4381 'type' => 'css', 4382 4382 ), 4383 4383 '2c1dba03' => ··· 4569 4569 ), 4570 4570 'reverse' => 4571 4571 array( 4572 - 'aphront-dialog-view-css' => 'f350af41', 4573 - 'aphront-error-view-css' => 'f350af41', 4574 - 'aphront-list-filter-view-css' => 'f350af41', 4575 - 'aphront-pager-view-css' => 'f350af41', 4576 - 'aphront-panel-view-css' => 'f350af41', 4577 - 'aphront-table-view-css' => 'f350af41', 4578 - 'aphront-tokenizer-control-css' => 'f350af41', 4579 - 'aphront-tooltip-css' => 'f350af41', 4580 - 'aphront-typeahead-control-css' => 'f350af41', 4572 + 'aphront-dialog-view-css' => 'f0d63822', 4573 + 'aphront-error-view-css' => 'f0d63822', 4574 + 'aphront-list-filter-view-css' => 'f0d63822', 4575 + 'aphront-pager-view-css' => 'f0d63822', 4576 + 'aphront-panel-view-css' => 'f0d63822', 4577 + 'aphront-table-view-css' => 'f0d63822', 4578 + 'aphront-tokenizer-control-css' => 'f0d63822', 4579 + 'aphront-tooltip-css' => 'f0d63822', 4580 + 'aphront-typeahead-control-css' => 'f0d63822', 4581 4581 'differential-changeset-view-css' => '1084b12b', 4582 4582 'differential-core-view-css' => '1084b12b', 4583 4583 'differential-inline-comment-editor' => '5e9e5c4e', ··· 4591 4591 'differential-table-of-contents-css' => '1084b12b', 4592 4592 'diffusion-commit-view-css' => '7aa115b4', 4593 4593 'diffusion-icons-css' => '7aa115b4', 4594 - 'global-drag-and-drop-css' => 'f350af41', 4594 + 'global-drag-and-drop-css' => 'f0d63822', 4595 4595 'inline-comment-summary-css' => '1084b12b', 4596 4596 'javelin-aphlict' => '2c1dba03', 4597 4597 'javelin-behavior' => '3e3be199', ··· 4666 4666 'javelin-util' => '3e3be199', 4667 4667 'javelin-vector' => '3e3be199', 4668 4668 'javelin-workflow' => '3e3be199', 4669 - 'lightbox-attachment-css' => 'f350af41', 4669 + 'lightbox-attachment-css' => 'f0d63822', 4670 4670 'maniphest-task-summary-css' => '49898640', 4671 - 'phabricator-action-list-view-css' => 'f350af41', 4672 - 'phabricator-application-launch-view-css' => 'f350af41', 4671 + 'phabricator-action-list-view-css' => 'f0d63822', 4672 + 'phabricator-application-launch-view-css' => 'f0d63822', 4673 4673 'phabricator-busy' => '2c1dba03', 4674 4674 'phabricator-content-source-view-css' => '1084b12b', 4675 - 'phabricator-core-css' => 'f350af41', 4676 - 'phabricator-crumbs-view-css' => 'f350af41', 4675 + 'phabricator-core-css' => 'f0d63822', 4676 + 'phabricator-crumbs-view-css' => 'f0d63822', 4677 4677 'phabricator-drag-and-drop-file-upload' => '5e9e5c4e', 4678 4678 'phabricator-dropdown-menu' => '2c1dba03', 4679 4679 'phabricator-file-upload' => '2c1dba03', 4680 - 'phabricator-filetree-view-css' => 'f350af41', 4681 - 'phabricator-flag-css' => 'f350af41', 4680 + 'phabricator-filetree-view-css' => 'f0d63822', 4681 + 'phabricator-flag-css' => 'f0d63822', 4682 4682 'phabricator-hovercard' => '2c1dba03', 4683 - 'phabricator-jump-nav' => 'f350af41', 4683 + 'phabricator-jump-nav' => 'f0d63822', 4684 4684 'phabricator-keyboard-shortcut' => '2c1dba03', 4685 4685 'phabricator-keyboard-shortcut-manager' => '2c1dba03', 4686 - 'phabricator-main-menu-view' => 'f350af41', 4686 + 'phabricator-main-menu-view' => 'f0d63822', 4687 4687 'phabricator-menu-item' => '2c1dba03', 4688 - 'phabricator-nav-view-css' => 'f350af41', 4688 + 'phabricator-nav-view-css' => 'f0d63822', 4689 4689 'phabricator-notification' => '2c1dba03', 4690 - 'phabricator-notification-css' => 'f350af41', 4691 - 'phabricator-notification-menu-css' => 'f350af41', 4690 + 'phabricator-notification-css' => 'f0d63822', 4691 + 'phabricator-notification-menu-css' => 'f0d63822', 4692 4692 'phabricator-object-selector-css' => '1084b12b', 4693 4693 'phabricator-phtize' => '2c1dba03', 4694 4694 'phabricator-prefab' => '2c1dba03', 4695 4695 'phabricator-project-tag-css' => '49898640', 4696 - 'phabricator-remarkup-css' => 'f350af41', 4696 + 'phabricator-remarkup-css' => 'f0d63822', 4697 4697 'phabricator-shaped-request' => '5e9e5c4e', 4698 - 'phabricator-side-menu-view-css' => 'f350af41', 4699 - 'phabricator-standard-page-view' => 'f350af41', 4700 - 'phabricator-tag-view-css' => 'f350af41', 4698 + 'phabricator-side-menu-view-css' => 'f0d63822', 4699 + 'phabricator-standard-page-view' => 'f0d63822', 4700 + 'phabricator-tag-view-css' => 'f0d63822', 4701 4701 'phabricator-textareautils' => '2c1dba03', 4702 4702 'phabricator-tooltip' => '2c1dba03', 4703 - 'phabricator-transaction-view-css' => 'f350af41', 4704 - 'phabricator-zindex-css' => 'f350af41', 4705 - 'phui-button-css' => 'f350af41', 4706 - 'phui-form-css' => 'f350af41', 4707 - 'phui-form-view-css' => 'f350af41', 4708 - 'phui-header-view-css' => 'f350af41', 4709 - 'phui-icon-view-css' => 'f350af41', 4710 - 'phui-list-view-css' => 'f350af41', 4711 - 'phui-object-item-list-view-css' => 'f350af41', 4712 - 'phui-property-list-view-css' => 'f350af41', 4713 - 'phui-spacing-css' => 'f350af41', 4714 - 'sprite-apps-large-css' => 'f350af41', 4715 - 'sprite-gradient-css' => 'f350af41', 4716 - 'sprite-icons-css' => 'f350af41', 4717 - 'sprite-menu-css' => 'f350af41', 4718 - 'sprite-status-css' => 'f350af41', 4719 - 'syntax-highlighting-css' => 'f350af41', 4703 + 'phabricator-transaction-view-css' => 'f0d63822', 4704 + 'phabricator-zindex-css' => 'f0d63822', 4705 + 'phui-button-css' => 'f0d63822', 4706 + 'phui-form-css' => 'f0d63822', 4707 + 'phui-form-view-css' => 'f0d63822', 4708 + 'phui-header-view-css' => 'f0d63822', 4709 + 'phui-icon-view-css' => 'f0d63822', 4710 + 'phui-list-view-css' => 'f0d63822', 4711 + 'phui-object-item-list-view-css' => 'f0d63822', 4712 + 'phui-property-list-view-css' => 'f0d63822', 4713 + 'phui-spacing-css' => 'f0d63822', 4714 + 'sprite-apps-large-css' => 'f0d63822', 4715 + 'sprite-gradient-css' => 'f0d63822', 4716 + 'sprite-icons-css' => 'f0d63822', 4717 + 'sprite-menu-css' => 'f0d63822', 4718 + 'sprite-status-css' => 'f0d63822', 4719 + 'syntax-highlighting-css' => 'f0d63822', 4720 4720 ), 4721 4721 ));
+19 -1
src/__phutil_library_map__.php
··· 652 652 'FileReplyHandler' => 'applications/files/mail/FileReplyHandler.php', 653 653 'HarbormasterBuild' => 'applications/harbormaster/storage/build/HarbormasterBuild.php', 654 654 'HarbormasterBuildArtifact' => 'applications/harbormaster/storage/build/HarbormasterBuildArtifact.php', 655 + 'HarbormasterBuildCancelController' => 'applications/harbormaster/controller/HarbormasterBuildCancelController.php', 655 656 'HarbormasterBuildItem' => 'applications/harbormaster/storage/build/HarbormasterBuildItem.php', 656 657 'HarbormasterBuildItemQuery' => 'applications/harbormaster/query/HarbormasterBuildItemQuery.php', 657 658 'HarbormasterBuildLog' => 'applications/harbormaster/storage/build/HarbormasterBuildLog.php', 659 + 'HarbormasterBuildLogQuery' => 'applications/harbormaster/query/HarbormasterBuildLogQuery.php', 658 660 'HarbormasterBuildPlan' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php', 659 661 'HarbormasterBuildPlanEditor' => 'applications/harbormaster/editor/HarbormasterBuildPlanEditor.php', 660 662 'HarbormasterBuildPlanQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanQuery.php', ··· 667 669 'HarbormasterBuildStepQuery' => 'applications/harbormaster/query/HarbormasterBuildStepQuery.php', 668 670 'HarbormasterBuildTarget' => 'applications/harbormaster/storage/build/HarbormasterBuildTarget.php', 669 671 'HarbormasterBuildTargetQuery' => 'applications/harbormaster/query/HarbormasterBuildTargetQuery.php', 672 + 'HarbormasterBuildViewController' => 'applications/harbormaster/controller/HarbormasterBuildViewController.php', 670 673 'HarbormasterBuildWorker' => 'applications/harbormaster/worker/HarbormasterBuildWorker.php', 671 674 'HarbormasterBuildable' => 'applications/harbormaster/storage/HarbormasterBuildable.php', 672 675 'HarbormasterBuildableApplyController' => 'applications/harbormaster/controller/HarbormasterBuildableApplyController.php', ··· 682 685 'HarbormasterObject' => 'applications/harbormaster/storage/HarbormasterObject.php', 683 686 'HarbormasterPHIDTypeBuild' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuild.php', 684 687 'HarbormasterPHIDTypeBuildItem' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildItem.php', 688 + 'HarbormasterPHIDTypeBuildLog' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildLog.php', 685 689 'HarbormasterPHIDTypeBuildPlan' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildPlan.php', 686 690 'HarbormasterPHIDTypeBuildStep' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildStep.php', 687 691 'HarbormasterPHIDTypeBuildTarget' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildTarget.php', ··· 1392 1396 'PhabricatorGlobalUploadTargetView' => 'applications/files/view/PhabricatorGlobalUploadTargetView.php', 1393 1397 'PhabricatorHandleObjectSelectorDataView' => 'applications/phid/handle/view/PhabricatorHandleObjectSelectorDataView.php', 1394 1398 'PhabricatorHandleQuery' => 'applications/phid/query/PhabricatorHandleQuery.php', 1399 + 'PhabricatorHarbormasterConfigOptions' => 'applications/harbormaster/config/PhabricatorHarbormasterConfigOptions.php', 1395 1400 'PhabricatorHash' => 'infrastructure/util/PhabricatorHash.php', 1396 1401 'PhabricatorHashTestCase' => 'infrastructure/util/__tests__/PhabricatorHashTestCase.php', 1397 1402 'PhabricatorHelpController' => 'applications/help/controller/PhabricatorHelpController.php', ··· 2254 2259 'ReleephStatusFieldSpecification' => 'applications/releeph/field/specification/ReleephStatusFieldSpecification.php', 2255 2260 'ReleephSummaryFieldSpecification' => 'applications/releeph/field/specification/ReleephSummaryFieldSpecification.php', 2256 2261 'ReleephUserView' => 'applications/releeph/view/user/ReleephUserView.php', 2262 + 'ShellLogView' => 'applications/harbormaster/view/ShellLogView.php', 2257 2263 'SleepBuildStepImplementation' => 'applications/harbormaster/step/SleepBuildStepImplementation.php', 2258 2264 'SlowvoteEmbedView' => 'applications/slowvote/view/SlowvoteEmbedView.php', 2259 2265 'SlowvoteRemarkupRule' => 'applications/slowvote/remarkup/SlowvoteRemarkupRule.php', 2266 + 'VariableBuildStepImplementation' => 'applications/harbormaster/step/VariableBuildStepImplementation.php', 2260 2267 ), 2261 2268 'function' => 2262 2269 array( ··· 2920 2927 0 => 'HarbormasterDAO', 2921 2928 1 => 'PhabricatorPolicyInterface', 2922 2929 ), 2930 + 'HarbormasterBuildCancelController' => 'HarbormasterController', 2923 2931 'HarbormasterBuildItem' => 'HarbormasterDAO', 2924 2932 'HarbormasterBuildItemQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 2925 - 'HarbormasterBuildLog' => 'HarbormasterDAO', 2933 + 'HarbormasterBuildLog' => 2934 + array( 2935 + 0 => 'HarbormasterDAO', 2936 + 1 => 'PhabricatorPolicyInterface', 2937 + ), 2938 + 'HarbormasterBuildLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 2926 2939 'HarbormasterBuildPlan' => 2927 2940 array( 2928 2941 0 => 'HarbormasterDAO', ··· 2944 2957 'HarbormasterBuildStepQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 2945 2958 'HarbormasterBuildTarget' => 'HarbormasterDAO', 2946 2959 'HarbormasterBuildTargetQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 2960 + 'HarbormasterBuildViewController' => 'HarbormasterController', 2947 2961 'HarbormasterBuildWorker' => 'PhabricatorWorker', 2948 2962 'HarbormasterBuildable' => 2949 2963 array( ··· 2967 2981 'HarbormasterObject' => 'HarbormasterDAO', 2968 2982 'HarbormasterPHIDTypeBuild' => 'PhabricatorPHIDType', 2969 2983 'HarbormasterPHIDTypeBuildItem' => 'PhabricatorPHIDType', 2984 + 'HarbormasterPHIDTypeBuildLog' => 'PhabricatorPHIDType', 2970 2985 'HarbormasterPHIDTypeBuildPlan' => 'PhabricatorPHIDType', 2971 2986 'HarbormasterPHIDTypeBuildStep' => 'PhabricatorPHIDType', 2972 2987 'HarbormasterPHIDTypeBuildTarget' => 'PhabricatorPHIDType', ··· 3776 3791 'PhabricatorGlobalLock' => 'PhutilLock', 3777 3792 'PhabricatorGlobalUploadTargetView' => 'AphrontView', 3778 3793 'PhabricatorHandleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 3794 + 'PhabricatorHarbormasterConfigOptions' => 'PhabricatorApplicationConfigOptions', 3779 3795 'PhabricatorHashTestCase' => 'PhabricatorTestCase', 3780 3796 'PhabricatorHelpController' => 'PhabricatorController', 3781 3797 'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController', ··· 4783 4799 'ReleephStatusFieldSpecification' => 'ReleephFieldSpecification', 4784 4800 'ReleephSummaryFieldSpecification' => 'ReleephFieldSpecification', 4785 4801 'ReleephUserView' => 'AphrontView', 4802 + 'ShellLogView' => 'AphrontView', 4786 4803 'SleepBuildStepImplementation' => 'BuildStepImplementation', 4787 4804 'SlowvoteEmbedView' => 'AphrontView', 4788 4805 'SlowvoteRemarkupRule' => 'PhabricatorRemarkupRuleObject', 4806 + 'VariableBuildStepImplementation' => 'BuildStepImplementation', 4789 4807 ), 4790 4808 ));
+4
src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php
··· 51 51 'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterStepEditController', 52 52 'delete/(?:(?P<id>\d+)/)?' => 'HarbormasterStepDeleteController', 53 53 ), 54 + 'build/' => array( 55 + '(?:(?P<id>\d+)/)?' => 'HarbormasterBuildViewController', 56 + 'cancel/(?:(?P<id>\d+)/)?' => 'HarbormasterBuildCancelController', 57 + ), 54 58 'plan/' => array( 55 59 '(?:query/(?P<queryKey>[^/]+)/)?' 56 60 => 'HarbormasterPlanListController',
+35
src/applications/harbormaster/config/PhabricatorHarbormasterConfigOptions.php
··· 1 + <?php 2 + 3 + final class PhabricatorHarbormasterConfigOptions 4 + extends PhabricatorApplicationConfigOptions { 5 + 6 + public function getName() { 7 + return pht('Harbormaster'); 8 + } 9 + 10 + public function getDescription() { 11 + return pht('Configure Harbormaster build engine.'); 12 + } 13 + 14 + public function getOptions() { 15 + return array( 16 + $this->newOption( 17 + 'harbormaster.temporary.hosts.whitelist', 18 + 'list<string>', 19 + array()) 20 + ->setSummary('Temporary configuration value.') 21 + ->setLocked(true) 22 + ->setDescription( 23 + pht( 24 + "This specifies a whitelist of remote hosts that the \"Run ". 25 + "Remote Command\" may connect to. This is a temporary ". 26 + "configuration option as Drydock is not yet available.". 27 + "\n\n". 28 + "**This configuration option will be removed in the future and ". 29 + "your build configuration will no longer work when Drydock ". 30 + "replaces this option. There is ABSOLUTELY NO SUPPORT for ". 31 + "using this functionality!**")) 32 + ); 33 + } 34 + 35 + }
+49
src/applications/harbormaster/controller/HarbormasterBuildCancelController.php
··· 1 + <?php 2 + 3 + final class HarbormasterBuildCancelController 4 + extends HarbormasterController { 5 + 6 + private $id; 7 + 8 + public function willProcessRequest(array $data) { 9 + $this->id = $data['id']; 10 + } 11 + 12 + public function processRequest() { 13 + $request = $this->getRequest(); 14 + $viewer = $request->getUser(); 15 + 16 + $id = $this->id; 17 + 18 + $build = id(new HarbormasterBuildQuery()) 19 + ->setViewer($viewer) 20 + ->withIDs(array($id)) 21 + ->executeOne(); 22 + if ($build === null) { 23 + return new Aphront404Response(); 24 + } 25 + 26 + $build_uri = $this->getApplicationURI('/build/'.$build->getID()); 27 + 28 + if ($request->isDialogFormPost()) { 29 + $build->setCancelRequested(true); 30 + $build->save(); 31 + 32 + return id(new AphrontRedirectResponse())->setURI($build_uri); 33 + } 34 + 35 + $dialog = new AphrontDialogView(); 36 + $dialog->setTitle(pht('Really cancel build?')) 37 + ->setUser($viewer) 38 + ->addSubmitButton(pht('Cancel')) 39 + ->addCancelButton($build_uri, pht('Don\'t Cancel')); 40 + $dialog->appendChild( 41 + phutil_tag( 42 + 'p', 43 + array(), 44 + pht( 45 + 'Really cancel this build?'))); 46 + return id(new AphrontDialogResponse())->setDialog($dialog); 47 + } 48 + 49 + }
+218
src/applications/harbormaster/controller/HarbormasterBuildViewController.php
··· 1 + <?php 2 + 3 + final class HarbormasterBuildViewController 4 + extends HarbormasterController { 5 + 6 + private $id; 7 + 8 + public function willProcessRequest(array $data) { 9 + $this->id = $data['id']; 10 + } 11 + 12 + public function processRequest() { 13 + $request = $this->getRequest(); 14 + $viewer = $request->getUser(); 15 + 16 + $id = $this->id; 17 + 18 + $build = id(new HarbormasterBuildQuery()) 19 + ->setViewer($viewer) 20 + ->withIDs(array($id)) 21 + ->executeOne(); 22 + if (!$build) { 23 + return new Aphront404Response(); 24 + } 25 + 26 + $title = pht("Build %d", $id); 27 + 28 + $header = id(new PHUIHeaderView()) 29 + ->setHeader($title) 30 + ->setUser($viewer) 31 + ->setPolicyObject($build); 32 + 33 + $box = id(new PHUIObjectBoxView()) 34 + ->setHeader($header); 35 + 36 + $actions = $this->buildActionList($build); 37 + $this->buildPropertyLists($box, $build, $actions); 38 + 39 + $crumbs = $this->buildApplicationCrumbs(); 40 + $crumbs->addCrumb( 41 + id(new PhabricatorCrumbView()) 42 + ->setName($title)); 43 + 44 + $logs = $this->buildLog($build); 45 + 46 + return $this->buildApplicationPage( 47 + array( 48 + $crumbs, 49 + $box, 50 + $logs 51 + ), 52 + array( 53 + 'title' => $title, 54 + 'device' => true, 55 + )); 56 + } 57 + 58 + private function buildLog(HarbormasterBuild $build) { 59 + $request = $this->getRequest(); 60 + $viewer = $request->getUser(); 61 + $limit = $request->getInt('l', 25); 62 + 63 + $logs = id(new HarbormasterBuildLogQuery()) 64 + ->setViewer($viewer) 65 + ->withBuildPHIDs(array($build->getPHID())) 66 + ->execute(); 67 + 68 + $log_boxes = array(); 69 + foreach ($logs as $log) { 70 + $start = 1; 71 + $lines = preg_split("/\r\n|\r|\n/", $log->getLogText()); 72 + if ($limit !== 0) { 73 + $start = count($lines) - $limit; 74 + if ($start >= 1) { 75 + $lines = array_slice($lines, -$limit, $limit); 76 + } else { 77 + $start = 1; 78 + } 79 + } 80 + $log_view = new ShellLogView(); 81 + $log_view->setLines($lines); 82 + $log_view->setStart($start); 83 + 84 + $header = id(new PHUIHeaderView()) 85 + ->setHeader(pht( 86 + 'Build Log %d (%s - %s)', 87 + $log->getID(), 88 + $log->getLogSource(), 89 + $log->getLogType())) 90 + ->setSubheader($this->createLogHeader($build, $log)) 91 + ->setUser($viewer); 92 + 93 + $log_boxes[] = id(new PHUIObjectBoxView()) 94 + ->setHeader($header) 95 + ->setForm($log_view); 96 + } 97 + 98 + return $log_boxes; 99 + } 100 + 101 + private function createLogHeader($build, $log) { 102 + $request = $this->getRequest(); 103 + $limit = $request->getInt('l', 25); 104 + 105 + $lines_25 = $this->getApplicationURI('/build/'.$build->getID().'/?l=25'); 106 + $lines_50 = $this->getApplicationURI('/build/'.$build->getID().'/?l=50'); 107 + $lines_100 = 108 + $this->getApplicationURI('/build/'.$build->getID().'/?l=100'); 109 + $lines_0 = $this->getApplicationURI('/build/'.$build->getID().'/?l=0'); 110 + 111 + $link_25 = phutil_tag('a', array('href' => $lines_25), pht('25')); 112 + $link_50 = phutil_tag('a', array('href' => $lines_50), pht('50')); 113 + $link_100 = phutil_tag('a', array('href' => $lines_100), pht('100')); 114 + $link_0 = phutil_tag('a', array('href' => $lines_0), pht('Unlimited')); 115 + 116 + if ($limit === 25) { 117 + $link_25 = phutil_tag('strong', array(), $link_25); 118 + } else if ($limit === 50) { 119 + $link_50 = phutil_tag('strong', array(), $link_50); 120 + } else if ($limit === 100) { 121 + $link_100 = phutil_tag('strong', array(), $link_100); 122 + } else if ($limit === 0) { 123 + $link_0 = phutil_tag('strong', array(), $link_0); 124 + } 125 + 126 + return phutil_tag( 127 + 'span', 128 + array(), 129 + array( 130 + $link_25, 131 + ' - ', 132 + $link_50, 133 + ' - ', 134 + $link_100, 135 + ' - ', 136 + $link_0, 137 + ' Lines')); 138 + } 139 + 140 + private function buildActionList(HarbormasterBuild $build) { 141 + $request = $this->getRequest(); 142 + $viewer = $request->getUser(); 143 + $id = $build->getID(); 144 + 145 + $list = id(new PhabricatorActionListView()) 146 + ->setUser($viewer) 147 + ->setObject($build) 148 + ->setObjectURI("/build/{$id}"); 149 + 150 + $action = 151 + id(new PhabricatorActionView()) 152 + ->setName(pht('Cancel Build')) 153 + ->setIcon('delete'); 154 + switch ($build->getBuildStatus()) { 155 + case HarbormasterBuild::STATUS_PENDING: 156 + case HarbormasterBuild::STATUS_WAITING: 157 + case HarbormasterBuild::STATUS_BUILDING: 158 + $cancel_uri = $this->getApplicationURI('/build/cancel/'.$id.'/'); 159 + $action 160 + ->setHref($cancel_uri) 161 + ->setWorkflow(true); 162 + break; 163 + default: 164 + $action 165 + ->setDisabled(true); 166 + break; 167 + } 168 + $list->addAction($action); 169 + 170 + return $list; 171 + } 172 + 173 + private function buildPropertyLists( 174 + PHUIObjectBoxView $box, 175 + HarbormasterBuild $build, 176 + PhabricatorActionListView $actions) { 177 + $request = $this->getRequest(); 178 + $viewer = $request->getUser(); 179 + 180 + $properties = id(new PHUIPropertyListView()) 181 + ->setUser($viewer) 182 + ->setObject($build) 183 + ->setActionList($actions); 184 + $box->addPropertyList($properties); 185 + 186 + $properties->addProperty( 187 + pht('Status'), 188 + $this->getStatus($build)); 189 + 190 + } 191 + 192 + private function getStatus(HarbormasterBuild $build) { 193 + if ($build->getCancelRequested()) { 194 + return pht('Cancelling'); 195 + } 196 + switch ($build->getBuildStatus()) { 197 + case HarbormasterBuild::STATUS_INACTIVE: 198 + return pht('Inactive'); 199 + case HarbormasterBuild::STATUS_PENDING: 200 + return pht('Pending'); 201 + case HarbormasterBuild::STATUS_WAITING: 202 + return pht('Waiting on Resource'); 203 + case HarbormasterBuild::STATUS_BUILDING: 204 + return pht('Building'); 205 + case HarbormasterBuild::STATUS_PASSED: 206 + return pht('Passed'); 207 + case HarbormasterBuild::STATUS_FAILED: 208 + return pht('Failed'); 209 + case HarbormasterBuild::STATUS_ERROR: 210 + return pht('Unexpected Error'); 211 + case HarbormasterBuild::STATUS_CANCELLED: 212 + return pht('Cancelled'); 213 + default: 214 + return pht('Unknown'); 215 + } 216 + } 217 + 218 + }
+41 -30
src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
··· 34 34 $build_list = id(new PHUIObjectItemListView()) 35 35 ->setUser($viewer); 36 36 foreach ($builds as $build) { 37 + $view_uri = $this->getApplicationURI('/build/'.$build->getID().'/'); 37 38 $item = id(new PHUIObjectItemView()) 38 39 ->setObjectName(pht('Build %d', $build->getID())) 39 - ->setHeader($build->getName()); 40 - switch ($build->getBuildStatus()) { 41 - case HarbormasterBuild::STATUS_INACTIVE: 42 - $item->setBarColor('grey'); 43 - $item->addAttribute(pht('Inactive')); 44 - break; 45 - case HarbormasterBuild::STATUS_PENDING: 46 - $item->setBarColor('blue'); 47 - $item->addAttribute(pht('Pending')); 48 - break; 49 - case HarbormasterBuild::STATUS_WAITING: 50 - $item->setBarColor('blue'); 51 - $item->addAttribute(pht('Waiting on Resource')); 52 - break; 53 - case HarbormasterBuild::STATUS_BUILDING: 54 - $item->setBarColor('yellow'); 55 - $item->addAttribute(pht('Building')); 56 - break; 57 - case HarbormasterBuild::STATUS_PASSED: 58 - $item->setBarColor('green'); 59 - $item->addAttribute(pht('Passed')); 60 - break; 61 - case HarbormasterBuild::STATUS_FAILED: 62 - $item->setBarColor('red'); 63 - $item->addAttribute(pht('Failed')); 64 - break; 65 - case HarbormasterBuild::STATUS_ERROR: 66 - $item->setBarColor('red'); 67 - $item->addAttribute(pht('Unexpected Error')); 68 - break; 40 + ->setHeader($build->getName()) 41 + ->setHref($view_uri); 42 + if ($build->getCancelRequested()) { 43 + $item->setBarColor('black'); 44 + $item->addAttribute(pht('Cancelling')); 45 + } else { 46 + switch ($build->getBuildStatus()) { 47 + case HarbormasterBuild::STATUS_INACTIVE: 48 + $item->setBarColor('grey'); 49 + $item->addAttribute(pht('Inactive')); 50 + break; 51 + case HarbormasterBuild::STATUS_PENDING: 52 + $item->setBarColor('blue'); 53 + $item->addAttribute(pht('Pending')); 54 + break; 55 + case HarbormasterBuild::STATUS_WAITING: 56 + $item->setBarColor('blue'); 57 + $item->addAttribute(pht('Waiting on Resource')); 58 + break; 59 + case HarbormasterBuild::STATUS_BUILDING: 60 + $item->setBarColor('yellow'); 61 + $item->addAttribute(pht('Building')); 62 + break; 63 + case HarbormasterBuild::STATUS_PASSED: 64 + $item->setBarColor('green'); 65 + $item->addAttribute(pht('Passed')); 66 + break; 67 + case HarbormasterBuild::STATUS_FAILED: 68 + $item->setBarColor('red'); 69 + $item->addAttribute(pht('Failed')); 70 + break; 71 + case HarbormasterBuild::STATUS_ERROR: 72 + $item->setBarColor('red'); 73 + $item->addAttribute(pht('Unexpected Error')); 74 + break; 75 + case HarbormasterBuild::STATUS_CANCELLED: 76 + $item->setBarColor('black'); 77 + $item->addAttribute(pht('Cancelled')); 78 + break; 79 + } 69 80 } 70 81 $build_list->addItem($item); 71 82 }
+5
src/applications/harbormaster/controller/HarbormasterStepEditController.php
··· 63 63 $form = id(new AphrontFormView()) 64 64 ->setUser($viewer); 65 65 66 + $instructions = $implementation->getSettingRemarkupInstructions(); 67 + if ($instructions !== null) { 68 + $form->appendRemarkupInstructions($instructions); 69 + } 70 + 66 71 // We need to render out all of the fields for the settings that 67 72 // the implementation has. 68 73 foreach ($implementation->getSettingDefinitions() as $name => $opt) {
+37
src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildLog.php
··· 1 + <?php 2 + 3 + final class HarbormasterPHIDTypeBuildLog extends PhabricatorPHIDType { 4 + 5 + const TYPECONST = 'HMCL'; 6 + 7 + public function getTypeConstant() { 8 + return self::TYPECONST; 9 + } 10 + 11 + public function getTypeName() { 12 + return pht('Build Log'); 13 + } 14 + 15 + public function newObject() { 16 + return new HarbormasterBuildLog(); 17 + } 18 + 19 + protected function buildQueryForObjects( 20 + PhabricatorObjectQuery $query, 21 + array $phids) { 22 + 23 + return id(new HarbormasterBuildLogQuery()) 24 + ->withPHIDs($phids); 25 + } 26 + 27 + public function loadHandles( 28 + PhabricatorHandleQuery $query, 29 + array $handles, 30 + array $objects) { 31 + 32 + foreach ($handles as $phid => $handle) { 33 + $build_log = $objects[$phid]; 34 + } 35 + } 36 + 37 + }
+98
src/applications/harbormaster/query/HarbormasterBuildLogQuery.php
··· 1 + <?php 2 + 3 + final class HarbormasterBuildLogQuery 4 + extends PhabricatorCursorPagedPolicyAwareQuery { 5 + 6 + private $ids; 7 + private $phids; 8 + private $buildPHIDs; 9 + 10 + public function withIDs(array $ids) { 11 + $this->ids = $ids; 12 + return $this; 13 + } 14 + 15 + public function withPHIDs(array $phids) { 16 + $this->phids = $phids; 17 + return $this; 18 + } 19 + 20 + public function withBuildPHIDs(array $build_phids) { 21 + $this->buildPHIDs = $build_phids; 22 + return $this; 23 + } 24 + 25 + protected function loadPage() { 26 + $table = new HarbormasterBuildLog(); 27 + $conn_r = $table->establishConnection('r'); 28 + 29 + $data = queryfx_all( 30 + $conn_r, 31 + 'SELECT * FROM %T %Q %Q %Q', 32 + $table->getTableName(), 33 + $this->buildWhereClause($conn_r), 34 + $this->buildOrderClause($conn_r), 35 + $this->buildLimitClause($conn_r)); 36 + 37 + return $table->loadAllFromArray($data); 38 + } 39 + 40 + protected function willFilterPage(array $page) { 41 + $builds = array(); 42 + 43 + $build_phids = array_filter(mpull($page, 'getBuildPHID')); 44 + if ($build_phids) { 45 + $builds = id(new HarbormasterBuildQuery()) 46 + ->setViewer($this->getViewer()) 47 + ->withPHIDs($build_phids) 48 + ->setParentQuery($this) 49 + ->execute(); 50 + $builds = mpull($builds, null, 'getPHID'); 51 + } 52 + 53 + foreach ($page as $key => $build_log) { 54 + $build_phid = $build_log->getBuildPHID(); 55 + if (empty($builds[$build_phid])) { 56 + unset($page[$key]); 57 + continue; 58 + } 59 + $build_log->attachBuild($builds[$build_phid]); 60 + } 61 + 62 + return $page; 63 + } 64 + 65 + private function buildWhereClause(AphrontDatabaseConnection $conn_r) { 66 + $where = array(); 67 + 68 + if ($this->ids) { 69 + $where[] = qsprintf( 70 + $conn_r, 71 + 'id IN (%Ld)', 72 + $this->ids); 73 + } 74 + 75 + if ($this->phids) { 76 + $where[] = qsprintf( 77 + $conn_r, 78 + 'phid IN (%Ls)', 79 + $this->phids); 80 + } 81 + 82 + if ($this->buildPHIDs) { 83 + $where[] = qsprintf( 84 + $conn_r, 85 + 'buildPHID IN (%Ls)', 86 + $this->buildPHIDs); 87 + } 88 + 89 + $where[] = $this->buildPagingClause($conn_r); 90 + 91 + return $this->formatWhereClause($where); 92 + } 93 + 94 + public function getQueryApplicationClass() { 95 + return 'PhabricatorApplicationHarbormaster'; 96 + } 97 + 98 + }
+10 -1
src/applications/harbormaster/step/BuildStepImplementation.php
··· 38 38 /** 39 39 * Run the build step against the specified build. 40 40 */ 41 - abstract public function execute(HarbormasterBuild $build); 41 + abstract public function execute( 42 + HarbormasterBuild $build, 43 + HarbormasterBuildStep $build_step); 42 44 43 45 /** 44 46 * Gets the settings for this build step. ··· 84 86 */ 85 87 public function getSettingDefinitions() { 86 88 return array(); 89 + } 90 + 91 + /** 92 + * Return relevant setting instructions as Remarkup. 93 + */ 94 + public function getSettingRemarkupInstructions() { 95 + return null; 87 96 } 88 97 }
+4 -1
src/applications/harbormaster/step/SleepBuildStepImplementation.php
··· 16 16 return pht('Sleep for %s seconds.', $settings['seconds']); 17 17 } 18 18 19 - public function execute(HarbormasterBuild $build) { 19 + public function execute( 20 + HarbormasterBuild $build, 21 + HarbormasterBuildStep $build_step) { 22 + 20 23 $settings = $this->getSettings(); 21 24 22 25 sleep($settings['seconds']);
+67
src/applications/harbormaster/step/VariableBuildStepImplementation.php
··· 1 + <?php 2 + 3 + abstract class VariableBuildStepImplementation extends BuildStepImplementation { 4 + 5 + public function retrieveVariablesFromBuild(HarbormasterBuild $build) { 6 + $results = array( 7 + 'revision' => null, 8 + 'commit' => null, 9 + 'repository' => null, 10 + 'vcs' => null, 11 + 'uri' => null, 12 + 'timestamp' => null); 13 + 14 + $buildable = $build->getBuildable(); 15 + $object = $buildable->getBuildableObject(); 16 + 17 + $repo = null; 18 + if ($object instanceof DifferentialRevision) { 19 + $results['revision'] = $object->getID(); 20 + $repo = $object->getRepository(); 21 + } else if ($object instanceof PhabricatorRepositoryCommit) { 22 + $results['commit'] = $object->getCommitIdentifier(); 23 + $repo = $object->getRepository(); 24 + } 25 + 26 + $results['repository'] = $repo->getCallsign(); 27 + $results['vcs'] = $repo->getVersionControlSystem(); 28 + $results['uri'] = $repo->getPublicRemoteURI(); 29 + $results['timestamp'] = time(); 30 + 31 + return $results; 32 + } 33 + 34 + public function mergeVariables(HarbormasterBuild $build, $string) { 35 + $variables = $this->retrieveVariablesFromBuild($build); 36 + foreach ($variables as $name => $value) { 37 + if ($value === null) { 38 + $value = ''; 39 + } 40 + $string = str_replace('${'.$name.'}', $value, $string); 41 + } 42 + return $string; 43 + } 44 + 45 + public function getAvailableVariables() { 46 + return array( 47 + 'revision' => pht('The differential revision ID, if applicable.'), 48 + 'commit' => pht('The commit identifier, if applicable.'), 49 + 'repository' => pht('The callsign of the repository in Phabricator.'), 50 + 'vcs' => pht('The version control system, either "svn", "hg" or "git".'), 51 + 'uri' => pht('The URI to clone or checkout the repository from.'), 52 + 'timestamp' => pht('The current UNIX timestamp.')); 53 + } 54 + 55 + public function getSettingRemarkupInstructions() { 56 + $text = ''; 57 + $text .= pht('The following variables are available: ')."\n"; 58 + $text .= "\n"; 59 + foreach ($this->getAvailableVariables() as $name => $desc) { 60 + $text .= ' - `'.$name.'`: '.$desc."\n"; 61 + } 62 + $text .= "\n"; 63 + $text .= "Use `\${name}` to merge a variable into a setting."; 64 + return $text; 65 + } 66 + 67 + }
+39 -1
src/applications/harbormaster/storage/build/HarbormasterBuild.php
··· 6 6 protected $buildablePHID; 7 7 protected $buildPlanPHID; 8 8 protected $buildStatus; 9 + protected $cancelRequested; 9 10 10 11 private $buildable = self::ATTACHABLE; 11 12 private $buildPlan = self::ATTACHABLE; ··· 45 46 */ 46 47 const STATUS_ERROR = 'error'; 47 48 49 + /** 50 + * The build has been cancelled. 51 + */ 52 + const STATUS_CANCELLED = 'cancelled'; 53 + 48 54 public static function initializeNewBuild(PhabricatorUser $actor) { 49 55 return id(new HarbormasterBuild()) 50 - ->setBuildStatus(self::STATUS_INACTIVE); 56 + ->setBuildStatus(self::STATUS_INACTIVE) 57 + ->setCancelRequested(false); 51 58 } 52 59 53 60 public function getConfiguration() { ··· 85 92 86 93 public function getBuildPlan() { 87 94 return $this->assertAttached($this->buildPlan); 95 + } 96 + 97 + public function createLog( 98 + HarbormasterBuildStep $build_step, 99 + $log_source, 100 + $log_type) { 101 + 102 + $log = HarbormasterBuildLog::initializeNewBuildLog($this, $build_step); 103 + $log->setLogSource($log_source); 104 + $log->setLogType($log_type); 105 + $log->save(); 106 + return $log; 107 + } 108 + 109 + /** 110 + * Checks for and handles build cancellation. If this method returns 111 + * true, the caller should stop any current operations and return control 112 + * as quickly as possible. 113 + */ 114 + public function checkForCancellation() { 115 + // Here we load a copy of the current build and check whether 116 + // the user requested cancellation. We can't do `reload()` here 117 + // in case there are changes that have not yet been saved. 118 + $copy = id(new HarbormasterBuild())->load($this->getID()); 119 + if ($copy->getCancelRequested()) { 120 + $this->setBuildStatus(HarbormasterBuild::STATUS_CANCELLED); 121 + $this->setCancelRequested(false); 122 + $this->save(); 123 + return true; 124 + } 125 + return false; 88 126 } 89 127 90 128
+201 -2
src/applications/harbormaster/storage/build/HarbormasterBuildLog.php
··· 1 1 <?php 2 2 3 - final class HarbormasterBuildLog extends HarbormasterDAO { 3 + final class HarbormasterBuildLog extends HarbormasterDAO 4 + implements PhabricatorPolicyInterface { 5 + 6 + protected $buildPHID; 7 + protected $buildStepPHID; 8 + protected $logSource; 9 + protected $logType; 10 + protected $duration; 11 + protected $live; 12 + 13 + private $build = self::ATTACHABLE; 14 + private $buildStep = self::ATTACHABLE; 15 + 16 + const CHUNK_BYTE_LIMIT = 102400; 17 + 18 + /** 19 + * The log is encoded as plain text. 20 + */ 21 + const ENCODING_TEXT = 'text'; 22 + 23 + public static function initializeNewBuildLog( 24 + HarbormasterBuild $build, 25 + HarbormasterBuildStep $build_step) { 26 + 27 + return id(new HarbormasterBuildLog()) 28 + ->setBuildPHID($build->getPHID()) 29 + ->setBuildStepPHID($build_step->getPHID()) 30 + ->setDuration(null) 31 + ->setLive(false); 32 + } 33 + 34 + public function getConfiguration() { 35 + return array( 36 + self::CONFIG_AUX_PHID => true, 37 + ) + parent::getConfiguration(); 38 + } 39 + 40 + public function generatePHID() { 41 + return PhabricatorPHID::generateNewPHID( 42 + HarbormasterPHIDTypeBuildLog::TYPECONST); 43 + } 44 + 45 + public function attachBuild(HarbormasterBuild $build) { 46 + $this->build = $build; 47 + return $this; 48 + } 49 + 50 + public function getBuild() { 51 + return $this->assertAttached($this->build); 52 + } 53 + 54 + public function getName() { 55 + return pht('Build Log'); 56 + } 57 + 58 + public function attachBuildStep( 59 + HarbormasterBuildStep $build_step = null) { 60 + $this->buildStep = $build_step; 61 + return $this; 62 + } 63 + 64 + public function getBuildStep() { 65 + return $this->assertAttached($this->buildStep); 66 + } 67 + 68 + public function start() { 69 + if ($this->getLive()) { 70 + throw new Exception("Live logging has already started for this log."); 71 + } 72 + 73 + $this->setLive(true); 74 + $this->save(); 75 + 76 + return time(); 77 + } 78 + 79 + public function append($content) { 80 + if (!$this->getLive()) { 81 + throw new Exception("Start logging before appending data to the log."); 82 + } 83 + if (strlen($content) === 0) { 84 + return; 85 + } 86 + 87 + // If the length of the content is greater than the chunk size limit, 88 + // then we can never fit the content in a single record. We need to 89 + // split our content out and call append on it for as many parts as there 90 + // are to the content. 91 + if (strlen($content) > self::CHUNK_BYTE_LIMIT) { 92 + $current = $content; 93 + while (strlen($current) > self::CHUNK_BYTE_LIMIT) { 94 + $part = substr($current, 0, self::CHUNK_BYTE_LIMIT); 95 + $current = substr($current, self::CHUNK_BYTE_LIMIT); 96 + $this->append($part); 97 + } 98 + return; 99 + } 100 + 101 + // Retrieve the size of last chunk from the DB for this log. If the 102 + // chunk is over 500K, then we need to create a new log entry. 103 + $conn = $this->establishConnection('w'); 104 + $result = queryfx_all( 105 + $conn, 106 + 'SELECT id, size, encoding '. 107 + 'FROM harbormaster_buildlogchunk '. 108 + 'WHERE logID = %d '. 109 + 'ORDER BY id DESC '. 110 + 'LIMIT 1', 111 + $this->getID()); 112 + if (count($result) === 0 || 113 + $result[0]["size"] + strlen($content) > self::CHUNK_BYTE_LIMIT || 114 + $result[0]["encoding"] !== self::ENCODING_TEXT) { 115 + 116 + // We must insert a new chunk because the data we are appending 117 + // won't fit into the existing one, or we don't have any existing 118 + // chunk data. 119 + queryfx( 120 + $conn, 121 + 'INSERT INTO harbormaster_buildlogchunk '. 122 + '(logID, encoding, size, chunk) '. 123 + 'VALUES '. 124 + '(%d, %s, %d, %s)', 125 + $this->getID(), 126 + self::ENCODING_TEXT, 127 + strlen($content), 128 + $content); 129 + } else { 130 + // We have a resulting record that we can append our content onto. 131 + queryfx( 132 + $conn, 133 + 'UPDATE harbormaster_buildlogchunk '. 134 + 'SET chunk = CONCAT(chunk, %s), size = LENGTH(CONCAT(chunk, %s))'. 135 + 'WHERE id = %d', 136 + $content, 137 + $content, 138 + $result[0]["id"]); 139 + } 140 + } 141 + 142 + public function finalize($start = 0) { 143 + if (!$this->getLive()) { 144 + throw new Exception("Start logging before finalizing it."); 145 + } 146 + 147 + // TODO: Encode the log contents in a gzipped format. 148 + $this->reload(); 149 + if ($start > 0) { 150 + $this->setDuration(time() - $start); 151 + } 152 + $this->setLive(false); 153 + $this->save(); 154 + } 155 + 156 + public function getLogText() { 157 + // TODO: This won't cope very well if we're pulling like a 700MB 158 + // log file out of the DB. We should probably implement some sort 159 + // of optional limit parameter so that when we're rendering out only 160 + // 25 lines in the UI, we don't wastefully read in the whole log. 161 + 162 + // We have to read our content out of the database and stitch all of 163 + // the log data back together. 164 + $conn = $this->establishConnection('r'); 165 + $result = queryfx_all( 166 + $conn, 167 + 'SELECT chunk '. 168 + 'FROM harbormaster_buildlogchunk '. 169 + 'WHERE logID = %d '. 170 + 'ORDER BY id ASC', 171 + $this->getID()); 4 172 5 - protected $buildItemPHID; 173 + $content = ""; 174 + foreach ($result as $row) { 175 + $content .= $row["chunk"]; 176 + } 177 + return $content; 178 + } 179 + 180 + 181 + /* -( PhabricatorPolicyInterface )----------------------------------------- */ 182 + 183 + 184 + public function getCapabilities() { 185 + return array( 186 + PhabricatorPolicyCapability::CAN_VIEW, 187 + ); 188 + } 189 + 190 + public function getPolicy($capability) { 191 + return $this->getBuild()->getPolicy($capability); 192 + } 193 + 194 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 195 + return $this->getBuild()->hasAutomaticCapability( 196 + $capability, 197 + $viewer); 198 + } 199 + 200 + public function describeAutomaticCapability($capability) { 201 + return pht( 202 + 'Users must be able to see a build to view it\'s build log.'); 203 + } 204 + 6 205 7 206 }
+101
src/applications/harbormaster/view/ShellLogView.php
··· 1 + <?php 2 + 3 + final class ShellLogView extends AphrontView { 4 + 5 + private $start = 1; 6 + private $lines; 7 + private $limit; 8 + private $highlights = array(); 9 + 10 + public function setStart($start) { 11 + $this->start = $start; 12 + return $this; 13 + } 14 + 15 + public function setLimit($limit) { 16 + $this->limit = $limit; 17 + return $this; 18 + } 19 + 20 + public function setLines(array $lines) { 21 + $this->lines = $lines; 22 + return $this; 23 + } 24 + 25 + public function setHighlights(array $highlights) { 26 + $this->highlights = array_fuse($highlights); 27 + return $this; 28 + } 29 + 30 + public function render() { 31 + require_celerity_resource('phabricator-source-code-view-css'); 32 + require_celerity_resource('syntax-highlighting-css'); 33 + 34 + Javelin::initBehavior('phabricator-oncopy', array()); 35 + 36 + $line_number = $this->start; 37 + 38 + $rows = array(); 39 + foreach ($this->lines as $line) { 40 + $hit_limit = $this->limit && 41 + ($line_number == $this->limit) && 42 + (count($this->lines) != $this->limit); 43 + 44 + if ($hit_limit) { 45 + $content_number = ''; 46 + $content_line = phutil_tag( 47 + 'span', 48 + array( 49 + 'class' => 'c', 50 + ), 51 + pht('...')); 52 + } else { 53 + $content_number = $line_number; 54 + $content_line = $line; 55 + } 56 + 57 + $row_attributes = array(); 58 + if (isset($this->highlights[$line_number])) { 59 + $row_attributes['class'] = 'phabricator-source-highlight'; 60 + } 61 + 62 + // TODO: Provide nice links. 63 + 64 + $rows[] = phutil_tag( 65 + 'tr', 66 + $row_attributes, 67 + hsprintf( 68 + '<th class="phabricator-source-line" '. 69 + 'style="background-color: #fff;">%s</th>'. 70 + '<td class="phabricator-source-code">%s</td>', 71 + $content_number, 72 + $content_line)); 73 + 74 + if ($hit_limit) { 75 + break; 76 + } 77 + 78 + $line_number++; 79 + } 80 + 81 + $classes = array(); 82 + $classes[] = 'phabricator-source-code-view'; 83 + $classes[] = 'remarkup-code'; 84 + $classes[] = 'PhabricatorMonospaced'; 85 + 86 + return phutil_tag( 87 + 'div', 88 + array( 89 + 'class' => 'phabricator-source-code-container', 90 + 'style' => 'background-color: black; color: white;' 91 + ), 92 + phutil_tag( 93 + 'table', 94 + array( 95 + 'class' => implode(' ', $classes), 96 + 'style' => 'background-color: black' 97 + ), 98 + phutil_implode_html('', $rows))); 99 + } 100 + 101 + }
+17 -1
src/applications/harbormaster/worker/HarbormasterBuildWorker.php
··· 25 25 pht('Invalid build ID "%s".', $id)); 26 26 } 27 27 28 + // It's possible for the user to request cancellation before 29 + // a worker picks up a build. We check to see if the build 30 + // is already cancelled, and return if it is. 31 + if ($build->checkForCancellation()) { 32 + return; 33 + } 34 + 28 35 try { 29 36 $build->setBuildStatus(HarbormasterBuild::STATUS_BUILDING); 30 37 $build->save(); ··· 44 51 $build->setBuildStatus(HarbormasterBuild::STATUS_ERROR); 45 52 break; 46 53 } 47 - $implementation->execute($build); 54 + $implementation->execute($build, $step); 48 55 if ($build->getBuildStatus() !== HarbormasterBuild::STATUS_BUILDING) { 49 56 break; 50 57 } 58 + if ($build->checkForCancellation()) { 59 + break; 60 + } 51 61 } 62 + 63 + // Check to see if the user requested cancellation. If they did and 64 + // we get to here, they might have either cancelled too late, or the 65 + // step isn't cancellation aware. In either case we ignore the result 66 + // and move to a cancelled state. 67 + $build->checkForCancellation(); 52 68 53 69 // If we get to here, then the build has finished. Set it to passed 54 70 // if no build step explicitly set the status.
+4
src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
··· 1744 1744 'type' => 'sql', 1745 1745 'name' => $this->getPatchPath('20131106.nuance-v0.sql'), 1746 1746 ), 1747 + '20131107.buildlog.sql' => array( 1748 + 'type' => 'sql', 1749 + 'name' => $this->getPatchPath('20131107.buildlog.sql'), 1750 + ), 1747 1751 ); 1748 1752 } 1749 1753 }