Compare commits

..

24 Commits

Author SHA1 Message Date
Tingluo Huang
f764f5dfa7 Release 2.301.1 runner with reverting #2333 (#2384)
* Revert "split by regex (#2333)"

This reverts commit 72830cfc12.

* Release 2.301.1 runner.
2023-01-18 19:38:19 -05:00
Tingluo Huang
e4716930a9 Create 2.301.0 runner release. 2023-01-18 15:04:38 -05:00
Tingluo Huang
6412390a22 Prepare 2.301.0 runner release. (#2382)
* Prepare 2.301.0 runner release.

* Update releaseNote.md

Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com>

* Update releaseNote.md

Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com>

* Update releaseNote.md

Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com>

* Update releaseNote.md

Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com>

* Update releaseNote.md

Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com>

* Update releaseNote.md

Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com>

* Update releaseNote.md

Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com>
2023-01-18 15:03:24 -05:00
John Sudol
7306014861 Update Node dependencies (#2381)
Co-authored-by: Tingluo Huang <tingluohuang@github.com>
2023-01-18 14:19:28 -05:00
John Sudol
d6f8633efc new option to remove local config files (#2367) 2023-01-18 11:28:43 -05:00
Cory Miller
130f6788d5 Add a disclaimer for which runner version is available to a given tenant (#2362)
* Add a disclaimer for which runner version is available to a given tenant

* Update releaseNote.md
2023-01-18 10:41:53 -05:00
John Sudol
9b390e0531 update node to 16.16.0 (#2371) 2023-01-18 10:41:15 -05:00
yujincat
a7101008a2 Show more information in the runner log (#2377)
* fix typo

* add workflow ref in the log

* show job name for all jobs

* update ref

* reflect the feedback

* fix a small bug
2023-01-18 10:40:35 -05:00
Tingluo Huang
4a6630531b Allow provide extra User-Agent for better correlation. (#2370) 2023-01-16 10:18:55 -05:00
Yang Cao
caec043085 Always upload to avoid issues (#2334)
* Remove unnecessary timelineId and timelineRecordId and use Guid stepId

* Log upload error to kusto

* Remove try-catch

* Using a well known telmetry record to avoid replacing issues

* fix Guid format
2022-12-28 11:56:53 -05:00
Tingluo Huang
a1244d2269 Add Header/Footer to multi-line message in StdoutTraceListener. (#2336) 2022-12-22 10:38:29 -05:00
Nikola Jokic
332b97f838 Treat jitconfig as secret. (#2335)
Co-authored-by: TingluoHuang <TingluoHuang@github.com>
2022-12-21 13:30:22 -05:00
Stefan Ruvceski
72830cfc12 split by regex (#2333)
* split by regex

* pr fix

* adding tests

* test fix
2022-12-20 14:28:29 +01:00
Tingluo Huang
29a28a870f Make runner image print diag log to STDOUT. (#2331) 2022-12-19 16:57:16 -05:00
Tingluo Huang
0dd7a113f1 Log GitHub RequestId for better traceability. (#2332) 2022-12-19 19:46:29 +00:00
TingluoHuang
83b8baa45e Bump runner version to 2.300.2 to match released version. 2022-12-19 14:22:51 -05:00
Ferenc Hammerl
d5e566ad17 Release notes for 2.300.1 (#2326)
* Update runnerversion

* Update releaseNote.md
2022-12-19 13:08:36 -05:00
Bethany
64381cca6a Re-add file size check prior to reading file (#2321) (#2330)
* Re-add file size check prior to reading file

* Remove redundant file size check
2022-12-19 12:56:28 -05:00
Stefan Ruvceski
f1b1532f32 set env in ProcessInvoker sanitized (#2280)
* set env in ProcessInvoker sanitized
2022-12-19 15:01:53 +01:00
Nikola Jokic
04761e5353 Initialize container manager based on whether the ContainerHooksPath is set (#2317)
* Added tests around checking if correct manager's Initialize method has been called

* repaired missing initialization on container action handler
2022-12-16 15:40:49 +01:00
Ava Stancu
f9e2fa939c Updated contact links for feature requests (#2314)
Users need to use the Github Community feedback page for all feature/enhancement requests.
2022-12-15 17:17:41 +02:00
Ferenc Hammerl
92acb625fb Update Dockerfile (#2315) 2022-12-15 15:44:07 +01:00
Ava Stancu
6b9e8a6be4 prepare release notes for 2.300.0 (#2312)
* Update runner version

* Update releaseNote.md

* Update releaseNote.md

* Update releaseNote.md

Co-authored-by: JoannaaKL <joannaakl@github.com>

* Update releaseNote.md

* Update releaseNote.md

Co-authored-by: JoannaaKL <joannaakl@github.com>
Co-authored-by: Ferenc Hammerl <31069338+fhammerl@users.noreply.github.com>
2022-12-14 10:33:02 +02:00
Brittany Ellich
f41f5d259d Use results for uploading step summaries (#2301)
* Use results service for uploading step summaries

* Use results summary over generic results naming convention

* Apply suggestions from code review

Co-authored-by: Tingluo Huang <tingluohuang@github.com>

* Addressing feedback

* Fix merge issue

* Remove empty line

* Update Results json objects to use snake case

* Adding the reference

Co-authored-by: Yang Cao <yacaovsnc@github.com>
Co-authored-by: Tingluo Huang <tingluohuang@github.com>
2022-12-14 09:28:33 +01:00
62 changed files with 4931 additions and 4408 deletions

View File

@@ -6,6 +6,9 @@
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
"ghcr.io/devcontainers/features/dotnet": {
"version": "6.0.300"
},
"ghcr.io/devcontainers/features/node:1": {
"version": "16"
}
},
"customizations": {

View File

@@ -1,5 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: 🛑 Request a feature in the runner application
url: https://github.com/orgs/community/discussions/categories/actions-and-packages
about: If you have feature requests for GitHub Actions, please use the Actions and Packages section on the Github Product Feedback page.
- name: ✅ Support for GitHub Actions
url: https://github.community/c/code-to-cloud/52
about: If you have questions about GitHub Actions or need support writing workflows, please ask in the GitHub Community Support forum.

View File

@@ -1,32 +0,0 @@
---
name: 🛑 Request a feature in the runner application
about: If you have feature requests for GitHub Actions, please use the "feedback and suggestions for GitHub Actions" link below.
title: ''
labels: enhancement
assignees: ''
---
<!--
👋 You're opening a request for an enhancement in the GitHub Actions **runner application**.
🛑 Please stop if you're not certain that the feature you want is in the runner application - if you have a suggestion for improving GitHub Actions, please see the [GitHub Actions Feedback](https://github.com/github/feedback/discussions/categories/actions-and-packages-feedback) discussion forum which is actively monitored. Using the forum ensures that we route your problem to the correct team. 😃
Some additional useful links:
* If you have found a security issue [please submit it here](https://hackerone.com/github)
* If you have questions or issues with the service, writing workflows or actions, then please [visit the GitHub Community Forum's Actions Board](https://github.community/t5/GitHub-Actions/bd-p/actions)
* If you are having an issue or have a question about GitHub Actions then please [contact customer support](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-github-actions#contacting-support)
If you have a feature request that is relevant to this repository, the runner, then please include the information below:
-->
**Describe the enhancement**
A clear and concise description of what the features or enhancement you need.
**Code Snippet**
If applicable, add a code snippet.
**Additional information**
Add any other context about the feature here.
NOTE: if the feature request has been agreed upon then the assignee will create an ADR. See docs/adrs/README.md

View File

@@ -35,7 +35,7 @@ All the configs below can be found in `.vscode/launch.json`.
If you launch `Run` or `Run [build]`, it starts a process called `Runner.Listener`.
This process will receive any job queued on this repository if the job runs on matching labels (e.g `runs-on: self-hosted`).
Once a job is received, a `Runner.Listener` starts a new process of `Runner.Worker`.
Since this is a diferent process, you can't use the same debugger session debug it.
Since this is a different process, you can't use the same debugger session debug it.
Instead, a parallel debugging session has to be started, using a different launch config.
Luckily, VS Code supports multiple parallel debugging sessions.

View File

@@ -2,7 +2,7 @@ FROM mcr.microsoft.com/dotnet/runtime-deps:6.0 as build
ARG RUNNER_VERSION
ARG RUNNER_ARCH="x64"
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.1.3
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.2.0
RUN apt update -y && apt install curl unzip -y
@@ -19,6 +19,7 @@ FROM mcr.microsoft.com/dotnet/runtime-deps:6.0
ENV RUNNER_ALLOW_RUNASROOT=1
ENV RUNNER_MANUALLY_TRAP_SIG=1
ENV ACTIONS_RUNNER_PRINT_LOG_TO_STDOUT=1
WORKDIR /actions-runner
COPY --from=build /actions-runner .

View File

@@ -1,18 +1,23 @@
## Features
- Displays the error logs in dedicated sub-sections of the Initialize containers section (#2182)
- Add generateServiceConfig option for configure command (#2226)
- Setting debug using GitHub Action variables (#2234)
- run.sh installs SIGINT and SIGTERM traps to gracefully stop runner (#2233, #2240)
- Log GitHub RequestId for better traceability (#2332)
- Dual upload summary to Actions and Result service (#2334)
- Allow providing extra User-Agent for better correlation (#2370)
- Show more information in the runner log (#2377)
- New option to remove local config files (#2367)
## Bugs
- Use Global.Variables instead of JobContext and include action path/ref in the message. (#2214)
- Treat jitconfig as secret (#2335)
- Add Header/Footer to multi-line message in StdoutTraceListener (#2336)
- Update Node dependencies (#2381)
## Misc
- Allow '--disableupdate' in create-latest-svc.sh (#2201)
- Fix markup for support link (#2114)
- Add runner devcontainer (#2187)
- Setup linter for Runner (#2211, #2213, #2216)
- Make runner image print diag log to STDOUT (#2331)
- Update Node.js to 16.16.0 (#2371)
- Add a disclaimer for which runner version is available to a given tenant (#2362)
_Note: Actions Runner follows a progressive release policy, so the latest release might not be available to your enterprise, organization, or repository yet.
To confirm which version of the Actions Runner you should expect, please view the download instructions for your enterprise, organization, or repository.
See https://docs.github.com/en/enterprise-cloud@latest/actions/hosting-your-own-runners/adding-self-hosted-runners_
## Windows x64
We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows.

View File

@@ -1 +1 @@
<Update to ./src/runnerversion when creating release>
2.301.1

View File

@@ -1 +1 @@
6ed30a2c1ee403a610d63e82bb230b9ba846a9c25cec9e4ea8672fb6ed4e1a51
3807dcbf947e840c33535fb466b096d76bf09e5c0254af8fc8cbbb24c6388222

View File

@@ -1 +1 @@
711c30c51ec52c9b7a9a2eb399d6ab2ab5ee1dc72de11879f2f36f919f163d78
ee01eee80cd8a460a4b9780ee13fdd20f25c59e754b4ccd99df55fbba2a85634

View File

@@ -1 +1 @@
a49479ca4b4988a06c097e8d22c51fd08a11c13f40807366236213d0e008cf6a
a9fb9c14e24e79aec97d4da197dd7bfc6364297d6fce573afb2df48cc9a931f8

View File

@@ -1 +1 @@
cc4708962a80325de0baa5ae8484e0cb9ae976ac6a4178c1c0d448b8c52bd7f7
a4e0e8fc62eba0967a39c7d693dcd0aeb8b2bed0765f9c38df80d42884f65341

View File

@@ -1 +1 @@
8e97df75230b843462a9b4c578ccec604ee4b4a1066120c85b04374317fa372b
17ac17fbe785b3d6fa2868d8d17185ebfe0c90b4b0ddf6b67eac70e42bcd989b

View File

@@ -1 +1 @@
e5dace2d41cc0682d096dcce4970079ad48ec7107e46195970eecfdb3df2acef
89f24657a550f1e818b0e9975e5b80edcf4dd22b7d4bccbb9e48e37f45d30fb1

View File

@@ -1 +1 @@
f75a671e5a188c76680739689aa75331a2c09d483dce9c80023518c48fd67a18
24fd131b5dce33ef16038b771407bc0507da8682a72fb3b7780607235f76db0b

View File

@@ -14,7 +14,7 @@
"devDependencies": {
"@types/node": "^12.7.12",
"@typescript-eslint/parser": "^5.15.0",
"@zeit/ncc": "^0.20.5",
"@vercel/ncc": "^0.36.0",
"eslint": "^8.11.0",
"eslint-plugin-github": "^4.3.5",
"prettier": "^1.19.1",
@@ -346,11 +346,10 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@zeit/ncc": {
"version": "0.20.5",
"resolved": "https://registry.npmjs.org/@zeit/ncc/-/ncc-0.20.5.tgz",
"integrity": "sha512-XU6uzwvv95DqxciQx+aOLhbyBx/13ky+RK1y88Age9Du3BlA4mMPCy13BGjayOrrumOzlq1XV3SD/BWiZENXlw==",
"deprecated": "@zeit/ncc is no longer maintained. Please use @vercel/ncc instead.",
"node_modules/@vercel/ncc": {
"version": "0.36.0",
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.36.0.tgz",
"integrity": "sha512-/ZTUJ/ZkRt694k7KJNimgmHjtQcRuVwsST2Z6XfYveQIuBbHR+EqkTc1jfgPkQmMyk/vtpxo3nVxe8CNuau86A==",
"dev": true,
"bin": {
"ncc": "dist/ncc/cli.js"
@@ -1722,9 +1721,9 @@
"dev": true
},
"node_modules/json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
"dependencies": {
"minimist": "^1.2.0"
@@ -1824,9 +1823,9 @@
}
},
"node_modules/minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -2747,10 +2746,10 @@
"eslint-visitor-keys": "^3.0.0"
}
},
"@zeit/ncc": {
"version": "0.20.5",
"resolved": "https://registry.npmjs.org/@zeit/ncc/-/ncc-0.20.5.tgz",
"integrity": "sha512-XU6uzwvv95DqxciQx+aOLhbyBx/13ky+RK1y88Age9Du3BlA4mMPCy13BGjayOrrumOzlq1XV3SD/BWiZENXlw==",
"@vercel/ncc": {
"version": "0.36.0",
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.36.0.tgz",
"integrity": "sha512-/ZTUJ/ZkRt694k7KJNimgmHjtQcRuVwsST2Z6XfYveQIuBbHR+EqkTc1jfgPkQmMyk/vtpxo3nVxe8CNuau86A==",
"dev": true
},
"acorn": {
@@ -3756,9 +3755,9 @@
"dev": true
},
"json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
"requires": {
"minimist": "^1.2.0"
@@ -3840,9 +3839,9 @@
}
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"requires": {
"brace-expansion": "^1.1.7"
}

View File

@@ -26,7 +26,7 @@
"devDependencies": {
"@types/node": "^12.7.12",
"@typescript-eslint/parser": "^5.15.0",
"@zeit/ncc": "^0.20.5",
"@vercel/ncc": "^0.36.0",
"eslint": "^8.11.0",
"eslint-plugin-github": "^4.3.5",
"prettier": "^1.19.1",

View File

@@ -5,7 +5,7 @@ PRECACHE=$2
NODE_URL=https://nodejs.org/dist
UNOFFICIAL_NODE_URL=https://unofficial-builds.nodejs.org/download/release
NODE12_VERSION="12.22.7"
NODE16_VERSION="16.13.0"
NODE16_VERSION="16.16.0"
get_abs_path() {
# exploits the fact that pwd will print abs path when no args

File diff suppressed because it is too large Load Diff

View File

@@ -90,7 +90,6 @@ namespace GitHub.Runner.Common
public static class Args
{
public static readonly string Auth = "auth";
public static readonly string JitConfig = "jitconfig";
public static readonly string Labels = "labels";
public static readonly string MonitorSocketAddress = "monitorsocketaddress";
public static readonly string Name = "name";
@@ -105,11 +104,13 @@ namespace GitHub.Runner.Common
public static readonly string Token = "token";
public static readonly string PAT = "pat";
public static readonly string WindowsLogonPassword = "windowslogonpassword";
public static readonly string JitConfig = "jitconfig";
public static string[] Secrets => new[]
{
PAT,
Token,
WindowsLogonPassword,
JitConfig,
};
}
@@ -130,6 +131,7 @@ namespace GitHub.Runner.Common
public static readonly string Ephemeral = "ephemeral";
public static readonly string GenerateServiceConfig = "generateServiceConfig";
public static readonly string Help = "help";
public static readonly string Local = "local";
public static readonly string Replace = "replace";
public static readonly string DisableUpdate = "disableupdate";
public static readonly string Once = "once"; // Keep this around since customers still relies on it
@@ -157,13 +159,16 @@ namespace GitHub.Runner.Common
}
public static readonly string InternalTelemetryIssueDataKey = "_internal_telemetry";
public static readonly Guid TelemetryRecordId = new Guid("11111111-1111-1111-1111-111111111111");
public static readonly string WorkerCrash = "WORKER_CRASH";
public static readonly string LowDiskSpace = "LOW_DISK_SPACE";
public static readonly string UnsupportedCommand = "UNSUPPORTED_COMMAND";
public static readonly string ResultsUploadFailure = "RESULTS_UPLOAD_FAILURE";
public static readonly string UnsupportedCommandMessage = "The `{0}` command is deprecated and will be disabled soon. Please upgrade to using Environment Files. For more information see: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/";
public static readonly string UnsupportedCommandMessageDisabled = "The `{0}` command is disabled. Please upgrade to using Environment Files or opt into unsecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_COMMANDS` environment variable to `true`. For more information see: https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/";
public static readonly string UnsupportedStopCommandTokenDisabled = "You cannot use a endToken that is an empty string, the string 'pause-logging', or another workflow command. For more information see: https://docs.github.com/actions/learn-github-actions/workflow-commands-for-github-actions#example-stopping-and-starting-workflow-commands or opt into insecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_STOPCOMMAND_TOKENS` environment variable to `true`.";
public static readonly string UnsupportedSummarySize = "$GITHUB_STEP_SUMMARY upload aborted, supports content up to a size of {0}k, got {1}k. For more information see: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summary";
public static readonly string SummaryUploadError = "$GITHUB_STEP_SUMMARY upload aborted, an error occurred when uploading the summary. For more information see: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summary";
public static readonly string Node12DetectedAfterEndOfLife = "Node.js 12 actions are deprecated. Please update the following actions to use Node.js 16: {0}. For more information see: https://github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/.";
}

View File

@@ -226,6 +226,20 @@ namespace GitHub.Runner.Common
}
_userAgents.Add(new ProductInfoHeaderValue("CommitSHA", BuildConstants.Source.CommitHash));
var extraUserAgent = Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_EXTRA_USER_AGENT");
if (!string.IsNullOrEmpty(extraUserAgent))
{
var extraUserAgentSplit = extraUserAgent.Split('/', StringSplitOptions.RemoveEmptyEntries);
if (extraUserAgentSplit.Length != 2)
{
_trace.Error($"GITHUB_ACTIONS_RUNNER_EXTRA_USER_AGENT is not in the format of 'name/version'.");
}
var extraUserAgentHeader = new ProductInfoHeaderValue(extraUserAgentSplit[0], extraUserAgentSplit[1]);
_trace.Info($"Adding extra user agent '{extraUserAgentHeader}' to all HTTP requests.");
_userAgents.Add(extraUserAgentHeader);
}
}
public string GetDirectory(WellKnownDirectory directory)

View File

@@ -13,6 +13,8 @@ using GitHub.Runner.Sdk;
using GitHub.Services.Common;
using GitHub.Services.WebApi;
using GitHub.Services.WebApi.Utilities.Internal;
using GitHub.Services.Results.Client;
using GitHub.Services.OAuth;
namespace GitHub.Runner.Common
{
@@ -22,11 +24,13 @@ namespace GitHub.Runner.Common
Task ConnectAsync(VssConnection jobConnection);
void InitializeWebsocketClient(ServiceEndpoint serviceEndpoint);
void InitializeResultsClient(Uri uri, string token);
// logging and console
Task<TaskLog> AppendLogContentAsync(Guid scopeIdentifier, string hubName, Guid planId, int logId, Stream uploadStream, CancellationToken cancellationToken);
Task AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, long? startLine, CancellationToken cancellationToken);
Task<TaskAttachment> CreateAttachmentAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, String type, String name, Stream uploadStream, CancellationToken cancellationToken);
Task CreateStepSymmaryAsync(string planId, string jobId, string stepId, string file, CancellationToken cancellationToken);
Task<TaskLog> CreateLogAsync(Guid scopeIdentifier, string hubName, Guid planId, TaskLog log, CancellationToken cancellationToken);
Task<Timeline> CreateTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken);
Task<List<TimelineRecord>> UpdateTimelineRecordsAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, IEnumerable<TimelineRecord> records, CancellationToken cancellationToken);
@@ -40,6 +44,7 @@ namespace GitHub.Runner.Common
private bool _hasConnection;
private VssConnection _connection;
private TaskHttpClient _taskClient;
private ResultsHttpClient _resultsClient;
private ClientWebSocket _websocketClient;
private ServiceEndpoint _serviceEndpoint;
@@ -143,6 +148,12 @@ namespace GitHub.Runner.Common
InitializeWebsocketClient(TimeSpan.Zero);
}
public void InitializeResultsClient(Uri uri, string token)
{
var httpMessageHandler = HostContext.CreateHttpClientHandler();
this._resultsClient = new ResultsHttpClient(uri, httpMessageHandler, token, disposeHandler: true);
}
public ValueTask DisposeAsync()
{
CloseWebSocket(WebSocketCloseStatus.NormalClosure, CancellationToken.None);
@@ -305,6 +316,16 @@ namespace GitHub.Runner.Common
return _taskClient.CreateAttachmentAsync(scopeIdentifier, hubName, planId, timelineId, timelineRecordId, type, name, uploadStream, cancellationToken: cancellationToken);
}
public Task CreateStepSymmaryAsync(string planId, string jobId, string stepId, string file, CancellationToken cancellationToken)
{
if (_resultsClient != null)
{
return _resultsClient.UploadStepSummaryAsync(planId, jobId, stepId, file, cancellationToken: cancellationToken);
}
throw new InvalidOperationException("Results client is not initialized.");
}
public Task<TaskLog> CreateLogAsync(Guid scopeIdentifier, string hubName, Guid planId, TaskLog log, CancellationToken cancellationToken)
{
CheckConnection();

View File

@@ -20,6 +20,7 @@ namespace GitHub.Runner.Common
void Start(Pipelines.AgentJobRequestMessage jobRequest);
void QueueWebConsoleLine(Guid stepRecordId, string line, long? lineNumber = null);
void QueueFileUpload(Guid timelineId, Guid timelineRecordId, string type, string name, string path, bool deleteSource);
void QueueSummaryUpload(Guid stepRecordId, string name, string path, bool deleteSource);
void QueueTimelineRecordUpdate(Guid timelineId, TimelineRecord timelineRecord);
}
@@ -30,6 +31,7 @@ namespace GitHub.Runner.Common
private static readonly TimeSpan _delayForWebConsoleLineDequeue = TimeSpan.FromMilliseconds(500);
private static readonly TimeSpan _delayForTimelineUpdateDequeue = TimeSpan.FromMilliseconds(500);
private static readonly TimeSpan _delayForFileUploadDequeue = TimeSpan.FromMilliseconds(1000);
private static readonly TimeSpan _delayForSummaryUploadDequeue = TimeSpan.FromMilliseconds(1000);
// Job message information
private Guid _scopeIdentifier;
@@ -44,6 +46,8 @@ namespace GitHub.Runner.Common
// queue for file upload (log file or attachment)
private readonly ConcurrentQueue<UploadFileInfo> _fileUploadQueue = new();
private readonly ConcurrentQueue<SummaryUploadFileInfo> _summaryFileUploadQueue = new();
// queue for timeline or timeline record update (one queue per timeline)
private readonly ConcurrentDictionary<Guid, ConcurrentQueue<TimelineRecord>> _timelineUpdateQueue = new();
@@ -56,6 +60,7 @@ namespace GitHub.Runner.Common
// Task for each queue's dequeue process
private Task _webConsoleLineDequeueTask;
private Task _fileUploadDequeueTask;
private Task _summaryUploadDequeueTask;
private Task _timelineUpdateDequeueTask;
// common
@@ -93,6 +98,20 @@ namespace GitHub.Runner.Common
_jobServer.InitializeWebsocketClient(serviceEndPoint);
// This code is usually wrapped by an instance of IExecutionContext which isn't available here.
jobRequest.Variables.TryGetValue("system.github.results_endpoint", out VariableValue resultsEndpointVariable);
var resultsReceiverEndpoint = resultsEndpointVariable?.Value;
if (serviceEndPoint?.Authorization != null &&
serviceEndPoint.Authorization.Parameters.TryGetValue("AccessToken", out var accessToken) &&
!string.IsNullOrEmpty(accessToken) &&
!string.IsNullOrEmpty(resultsReceiverEndpoint))
{
Trace.Info("Initializing results client");
_jobServer.InitializeResultsClient(new Uri(resultsReceiverEndpoint), accessToken);
}
if (_queueInProcess)
{
Trace.Info("No-opt, all queue process tasks are running.");
@@ -120,10 +139,13 @@ namespace GitHub.Runner.Common
Trace.Info("Start process file upload queue.");
_fileUploadDequeueTask = ProcessFilesUploadQueueAsync();
Trace.Info("Start results file upload queue.");
_summaryUploadDequeueTask = ProcessSummaryUploadQueueAsync();
Trace.Info("Start process timeline update queue.");
_timelineUpdateDequeueTask = ProcessTimelinesUpdateQueueAsync();
_allDequeueTasks = new Task[] { _webConsoleLineDequeueTask, _fileUploadDequeueTask, _timelineUpdateDequeueTask };
_allDequeueTasks = new Task[] { _webConsoleLineDequeueTask, _fileUploadDequeueTask, _timelineUpdateDequeueTask, _summaryUploadDequeueTask };
_queueInProcess = true;
}
@@ -154,6 +176,10 @@ namespace GitHub.Runner.Common
await ProcessFilesUploadQueueAsync(runOnce: true);
Trace.Info("File upload queue drained.");
Trace.Verbose("Draining results summary upload queue.");
await ProcessSummaryUploadQueueAsync(runOnce: true);
Trace.Info("Results summary upload queue drained.");
// ProcessTimelinesUpdateQueueAsync() will throw exception during shutdown
// if there is any timeline records that failed to update contains output variabls.
Trace.Verbose("Draining timeline update queue.");
@@ -204,6 +230,23 @@ namespace GitHub.Runner.Common
_fileUploadQueue.Enqueue(newFile);
}
public void QueueSummaryUpload(Guid stepRecordId, string name, string path, bool deleteSource)
{
// all parameter not null, file path exist.
var newFile = new SummaryUploadFileInfo()
{
Name = name,
Path = path,
PlanId = _planId.ToString(),
JobId = _jobTimelineRecordId.ToString(),
StepId = stepRecordId.ToString(),
DeleteSource = deleteSource
};
Trace.Verbose("Enqueue results file upload queue: file '{0}' attach to job {1} step {2}", newFile.Path, _jobTimelineRecordId, stepRecordId);
_summaryFileUploadQueue.Enqueue(newFile);
}
public void QueueTimelineRecordUpdate(Guid timelineId, TimelineRecord timelineRecord)
{
ArgUtil.NotEmpty(timelineId, nameof(timelineId));
@@ -394,6 +437,73 @@ namespace GitHub.Runner.Common
}
}
private async Task ProcessSummaryUploadQueueAsync(bool runOnce = false)
{
Trace.Info("Starting results-based upload queue...");
while (!_jobCompletionSource.Task.IsCompleted || runOnce)
{
List<SummaryUploadFileInfo> filesToUpload = new();
SummaryUploadFileInfo dequeueFile;
while (_summaryFileUploadQueue.TryDequeue(out dequeueFile))
{
filesToUpload.Add(dequeueFile);
// process at most 10 file upload.
if (!runOnce && filesToUpload.Count > 10)
{
break;
}
}
if (filesToUpload.Count > 0)
{
if (runOnce)
{
Trace.Info($"Uploading {filesToUpload.Count} summary files in one shot through results service.");
}
int errorCount = 0;
foreach (var file in filesToUpload)
{
try
{
await UploadSummaryFile(file);
}
catch (Exception ex)
{
var issue = new Issue() { Type = IssueType.Warning, Message = $"Caught exception during summary file upload to results. {ex.Message}" };
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.ResultsUploadFailure;
var telemetryRecord = new TimelineRecord()
{
Id = Constants.Runner.TelemetryRecordId,
};
telemetryRecord.Issues.Add(issue);
QueueTimelineRecordUpdate(_jobTimelineId, telemetryRecord);
Trace.Info("Catch exception during summary file upload to results, keep going since the process is best effort.");
Trace.Error(ex);
}
finally
{
errorCount++;
}
}
Trace.Info("Tried to upload {0} summary files to results, success rate: {1}/{0}.", filesToUpload.Count, filesToUpload.Count - errorCount);
}
if (runOnce)
{
break;
}
else
{
await Task.Delay(_delayForSummaryUploadDequeue);
}
}
}
private async Task ProcessTimelinesUpdateQueueAsync(bool runOnce = false)
{
while (!_jobCompletionSource.Task.IsCompleted || runOnce)
@@ -600,7 +710,8 @@ namespace GitHub.Runner.Common
{
foreach (var issue in record.Issues)
{
string source = issue["sourcepath"];
String source;
issue.Data.TryGetValue("sourcepath", out source);
Trace.Verbose($" Issue: c={issue.Category}, t={issue.Type}, s={source ?? string.Empty}, m={issue.Message}");
}
}
@@ -664,6 +775,35 @@ namespace GitHub.Runner.Common
}
}
}
private async Task UploadSummaryFile(SummaryUploadFileInfo file)
{
bool uploadSucceed = false;
try
{
// Upload the step summary
Trace.Info($"Starting to upload summary file to results service {file.Name}, {file.Path}");
var cancellationTokenSource = new CancellationTokenSource();
await _jobServer.CreateStepSymmaryAsync(file.PlanId, file.JobId, file.StepId, file.Path, cancellationTokenSource.Token);
uploadSucceed = true;
}
finally
{
if (uploadSucceed && file.DeleteSource)
{
try
{
File.Delete(file.Path);
}
catch (Exception ex)
{
Trace.Info("Catch exception during delete success results uploaded summary file.");
Trace.Error(ex);
}
}
}
}
}
internal class PendingTimelineRecord
@@ -682,6 +822,17 @@ namespace GitHub.Runner.Common
public bool DeleteSource { get; set; }
}
internal class SummaryUploadFileInfo
{
public string Name { get; set; }
public string Path { get; set; }
public string PlanId { get; set; }
public string JobId { get; set; }
public string StepId { get; set; }
public bool DeleteSource { get; set; }
}
internal class ConsoleLineInfo
{

View File

@@ -1,8 +1,8 @@
using GitHub.Runner.Sdk;
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using GitHub.Runner.Sdk;
namespace GitHub.Runner.Common
{
@@ -24,9 +24,16 @@ namespace GitHub.Runner.Common
return;
}
WriteHeader(source, eventType, id);
WriteLine(message);
WriteFooter(eventCache);
if (!string.IsNullOrEmpty(message))
{
var messageLines = message.Split(Environment.NewLine);
foreach (var messageLine in messageLines)
{
WriteHeader(source, eventType, id);
WriteLine(messageLine);
WriteFooter(eventCache);
}
}
}
internal bool IsEnabled(TraceOptions opts)
@@ -86,5 +93,4 @@ namespace GitHub.Runner.Common
IndentLevel--;
}
}
}
}

View File

@@ -56,7 +56,8 @@ namespace GitHub.Runner.Listener
new string[]
{
Constants.Runner.CommandLine.Args.Token,
Constants.Runner.CommandLine.Args.PAT
Constants.Runner.CommandLine.Args.PAT,
Constants.Runner.CommandLine.Flags.Local
},
// Valid run flags and args
[Constants.Runner.CommandLine.Commands.Run] =
@@ -86,6 +87,7 @@ namespace GitHub.Runner.Listener
public bool Help => TestFlag(Constants.Runner.CommandLine.Flags.Help);
public bool Unattended => TestFlag(Constants.Runner.CommandLine.Flags.Unattended);
public bool Version => TestFlag(Constants.Runner.CommandLine.Flags.Version);
public bool RemoveLocalConfig => TestFlag(Constants.Runner.CommandLine.Flags.Local);
// Keep this around since customers still relies on it
public bool RunOnce => TestFlag(Constants.Runner.CommandLine.Flags.Once);

View File

@@ -1,10 +1,3 @@
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using GitHub.Services.Common;
using GitHub.Services.Common.Internal;
using GitHub.Services.OAuth;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -14,6 +7,13 @@ using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using GitHub.Services.Common;
using GitHub.Services.Common.Internal;
using GitHub.Services.OAuth;
namespace GitHub.Runner.Listener.Configuration
{
@@ -636,7 +636,7 @@ namespace GitHub.Runner.Listener.Configuration
}
int retryCount = 0;
while(retryCount < 3)
while (retryCount < 3)
{
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
using (var httpClient = new HttpClient(httpClientHandler))
@@ -646,28 +646,29 @@ namespace GitHub.Runner.Listener.Configuration
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("basic", base64EncodingToken);
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/vnd.github.v3+json");
var responseStatus = System.Net.HttpStatusCode.OK;
try
{
var response = await httpClient.PostAsync(githubApiUrl, new StringContent(string.Empty));
responseStatus = response.StatusCode;
var githubRequestId = GetGitHubRequestId(response.Headers);
if (response.IsSuccessStatusCode)
{
Trace.Info($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'");
Trace.Info($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}' ({githubRequestId})");
var jsonResponse = await response.Content.ReadAsStringAsync();
return StringUtil.ConvertFromJson<GitHubRunnerRegisterToken>(jsonResponse);
}
else
{
_term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'");
_term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}' (Request Id: {githubRequestId})");
var errorResponse = await response.Content.ReadAsStringAsync();
_term.WriteError(errorResponse);
response.EnsureSuccessStatusCode();
}
}
catch(Exception ex) when (retryCount < 2 && responseStatus != System.Net.HttpStatusCode.NotFound)
catch (Exception ex) when (retryCount < 2 && responseStatus != System.Net.HttpStatusCode.NotFound)
{
retryCount++;
Trace.Error($"Failed to get JIT runner token -- Atempt: {retryCount}");
@@ -714,22 +715,23 @@ namespace GitHub.Runner.Listener.Configuration
{
var response = await httpClient.PostAsync(githubApiUrl, new StringContent(StringUtil.ConvertToJson(bodyObject), null, "application/json"));
responseStatus = response.StatusCode;
var githubRequestId = GetGitHubRequestId(response.Headers);
if(response.IsSuccessStatusCode)
if (response.IsSuccessStatusCode)
{
Trace.Info($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'");
Trace.Info($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}' ({githubRequestId})");
var jsonResponse = await response.Content.ReadAsStringAsync();
return StringUtil.ConvertFromJson<GitHubAuthResult>(jsonResponse);
}
else
{
_term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'");
_term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}' (Request Id: {githubRequestId})");
var errorResponse = await response.Content.ReadAsStringAsync();
_term.WriteError(errorResponse);
response.EnsureSuccessStatusCode();
}
}
catch(Exception ex) when (retryCount < 2 && responseStatus != System.Net.HttpStatusCode.NotFound)
catch (Exception ex) when (retryCount < 2 && responseStatus != System.Net.HttpStatusCode.NotFound)
{
retryCount++;
Trace.Error($"Failed to get tenant credentials -- Atempt: {retryCount}");
@@ -742,5 +744,14 @@ namespace GitHub.Runner.Listener.Configuration
}
return null;
}
private string GetGitHubRequestId(HttpResponseHeaders headers)
{
if (headers.TryGetValues("x-github-request-id", out var headerValues))
{
return headerValues.FirstOrDefault();
}
return string.Empty;
}
}
}

View File

@@ -33,7 +33,7 @@ namespace GitHub.Runner.Listener
// This implementation of IJobDispatcher is not thread safe.
// It is based on the fact that the current design of the runner is a dequeue
// and processes one message from the message queue at a time.
// In addition, it only executes one job every time,
// In addition, it only executes one job every time,
// and the server will not send another job while this one is still running.
public sealed class JobDispatcher : RunnerService, IJobDispatcher
{
@@ -426,7 +426,7 @@ namespace GitHub.Runner.Listener
{
workerOutput.Add(stdout.Data);
}
if (printToStdout)
{
term.WriteLine(stdout.Data, skipTracing: true);
@@ -512,7 +512,7 @@ namespace GitHub.Runner.Listener
var accessToken = systemConnection?.Authorization?.Parameters["AccessToken"];
notification.JobStarted(message.JobId, accessToken, systemConnection.Url);
HostContext.WritePerfCounter($"SentJobToWorker_{requestId}");
HostContext.WritePerfCounter($"SentJobToWorker_{requestId.ToString()}");
try
{
@@ -620,7 +620,7 @@ namespace GitHub.Runner.Listener
}
}
// wait worker to exit
// wait worker to exit
// if worker doesn't exit within timeout, then kill worker.
completedTask = await Task.WhenAny(workerProcessTask, Task.Delay(-1, workerCancelTimeoutKillToken));
@@ -1014,7 +1014,7 @@ namespace GitHub.Runner.Listener
}
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = errorMessage };
unhandledExceptionIssue[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.WorkerCrash;
unhandledExceptionIssue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.WorkerCrash;
jobRecord.ErrorCount++;
jobRecord.Issues.Add(unhandledExceptionIssue);
await jobServer.UpdateTimelineRecordsAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, new TimelineRecord[] { jobRecord }, CancellationToken.None);

View File

@@ -135,6 +135,12 @@ namespace GitHub.Runner.Listener
// remove config files, remove service, and exit
if (command.Remove)
{
// only remove local config files and exit
if(command.RemoveLocalConfig)
{
configManager.DeleteLocalRunnerConfig();
return Constants.Runner.ReturnCode.Success;
}
try
{
await configManager.UnconfigureAsync(command);
@@ -647,6 +653,7 @@ Config Options:
--name string Name of the runner to configure (default {Environment.MachineName ?? "myrunner"})
--runnergroup string Name of the runner group to add this runner to (defaults to the default runner group)
--labels string Extra labels in addition to the default: 'self-hosted,{Constants.Runner.Platform},{Constants.Runner.PlatformArchitecture}'
--local Removes the runner config files from your local machine. Used as an option to the remove command
--work string Relative runner work directory (default {Constants.Path.WorkDirectory})
--replace Replace any existing runner with the same name (default false)
--pat GitHub personal access token with repo scope. Used for checking network connectivity when executing `.{separator}run.{ext} --check`

View File

@@ -264,7 +264,17 @@ namespace GitHub.Runner.Sdk
{
foreach (KeyValuePair<string, string> kvp in environment)
{
#if OS_WINDOWS
string tempKey = String.IsNullOrWhiteSpace(kvp.Key) ? kvp.Key : kvp.Key.Split('\0')[0];
string tempValue = String.IsNullOrWhiteSpace(kvp.Value) ? kvp.Value : kvp.Value.Split('\0')[0];
if(!String.IsNullOrWhiteSpace(tempKey))
{
_proc.StartInfo.Environment[tempKey] = tempValue;
}
#else
_proc.StartInfo.Environment[kvp.Key] = kvp.Value;
#endif
}
}

View File

@@ -270,9 +270,12 @@ namespace GitHub.Runner.Worker
if (string.Equals(blocked, envName, StringComparison.OrdinalIgnoreCase))
{
// Log Telemetry and let user know they shouldn't do this
var message = $"Can't update {blocked} environment variable using ::set-env:: command.";
var metadata = new IssueMetadata(Constants.Runner.InternalTelemetryIssueDataKey, $"{Constants.Runner.UnsupportedCommand}_{envName}");
var issue = context.CreateIssue(IssueType.Error, message, metadata, true);
var issue = new Issue()
{
Type = IssueType.Error,
Message = $"Can't update {blocked} environment variable using ::set-env:: command."
};
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = $"{Constants.Runner.UnsupportedCommand}_{envName}";
context.AddIssue(issue);
return;
@@ -306,9 +309,12 @@ namespace GitHub.Runner.Worker
{
if (context.Global.Variables.GetBoolean("DistributedTask.DeprecateStepOutputCommands") ?? false)
{
var message = string.Format(Constants.Runner.UnsupportedCommandMessage, this.Command);
var metadata = new IssueMetadata(Constants.Runner.InternalTelemetryIssueDataKey, Constants.Runner.UnsupportedCommand);
var issue = context.CreateIssue(IssueType.Warning, message, metadata, true);
var issue = new Issue()
{
Type = IssueType.Warning,
Message = String.Format(Constants.Runner.UnsupportedCommandMessage, this.Command)
};
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.UnsupportedCommand;
context.AddIssue(issue);
}
@@ -338,9 +344,12 @@ namespace GitHub.Runner.Worker
{
if (context.Global.Variables.GetBoolean("DistributedTask.DeprecateStepOutputCommands") ?? false)
{
var message = string.Format(Constants.Runner.UnsupportedCommandMessage, this.Command);
var metadata = new IssueMetadata(Constants.Runner.InternalTelemetryIssueDataKey, Constants.Runner.UnsupportedCommand);
var issue = context.CreateIssue(IssueType.Warning, message, metadata, true);
var issue = new Issue()
{
Type = IssueType.Warning,
Message = String.Format(Constants.Runner.UnsupportedCommandMessage, this.Command)
};
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.UnsupportedCommand;
context.AddIssue(issue);
}
@@ -609,11 +618,16 @@ namespace GitHub.Runner.Worker
context.Debug("Enhanced Annotations not enabled on the server. The 'title', 'end_line', and 'end_column' fields are unsupported.");
}
var issueCategory = "General";
Issue issue = new()
{
Category = "General",
Type = this.Type,
Message = command.Data
};
if (!string.IsNullOrEmpty(file))
{
issueCategory = "Code";
issue.Category = "Code";
if (container != null)
{
@@ -644,13 +658,14 @@ namespace GitHub.Runner.Worker
}
}
string keyToExclude = Constants.Runner.InternalTelemetryIssueDataKey;
var filteredDictionaryEntries = command.Properties
.Where(kvp => !string.Equals(kvp.Key, keyToExclude, StringComparison.OrdinalIgnoreCase))
.ToList();
foreach (var property in command.Properties)
{
if (!string.Equals(property.Key, Constants.Runner.InternalTelemetryIssueDataKey, StringComparison.OrdinalIgnoreCase))
{
issue.Data[property.Key] = property.Value;
}
}
var metadata = new IssueMetadata(issueCategory, false, null, filteredDictionaryEntries);
var issue = context.CreateIssue(this.Type, command.Data, metadata, true);
context.AddIssue(issue);
}

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression; // required for OS_WINDOWS
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;

View File

@@ -33,8 +33,14 @@ namespace GitHub.Runner.Worker
public override void Initialize(IHostContext hostContext)
{
base.Initialize(hostContext);
_dockerManager = HostContext.GetService<IDockerCommandManager>();
_containerHookManager = HostContext.GetService<IContainerHookManager>();
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(Constants.Hooks.ContainerHooksPath)))
{
_dockerManager = HostContext.GetService<IDockerCommandManager>();
}
else
{
_containerHookManager = HostContext.GetService<IContainerHookManager>();
}
}
public async Task StartContainersAsync(IExecutionContext executionContext, object data)

View File

@@ -5,6 +5,7 @@ using System.IO.Compression;
using GitHub.DistributedTask.WebApi;
using System.Linq;
using System.Globalization;
using System.Threading.Tasks;
using Pipelines = GitHub.DistributedTask.Pipelines;
using GitHub.Runner.Common;
using GitHub.Runner.Sdk;
@@ -31,7 +32,7 @@ namespace GitHub.Runner.Worker
{
private static string DateTimeFormat = "yyyyMMdd-HHmmss";
public void UploadDiagnosticLogs(IExecutionContext executionContext,
IExecutionContext parentContext,
IExecutionContext parentContext,
Pipelines.AgentJobRequestMessage message,
DateTime jobStartTimeUtc)
{

View File

@@ -16,7 +16,6 @@ using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using GitHub.Runner.Worker.Container;
using GitHub.Runner.Worker.Handlers;
using GitHub.Services.Common;
using Newtonsoft.Json;
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
using Pipelines = GitHub.DistributedTask.Pipelines;
@@ -81,6 +80,7 @@ namespace GitHub.Runner.Worker
// logging
long Write(string tag, string message);
void QueueAttachFile(string type, string name, string filePath);
void QueueSummaryFile(string name, string filePath, Guid stepRecordId);
// timeline record update methods
void Start(string currentOperation = null);
@@ -91,8 +91,7 @@ namespace GitHub.Runner.Worker
void SetGitHubContext(string name, string value);
void SetOutput(string name, string value, out string reference);
void SetTimeout(TimeSpan? timeout);
IReadOnlyIssue CreateIssue(IssueType issueType, string rawMessage, IssueMetadata metadata, bool writeToLog);
void AddIssue(IReadOnlyIssue issue);
void AddIssue(Issue issue, string message = null);
void Progress(int percentage, string currentOperation = null);
void UpdateDetailTimelineRecord(TimelineRecord record);
@@ -126,10 +125,8 @@ namespace GitHub.Runner.Worker
private readonly TimelineRecord _record = new();
private readonly Dictionary<Guid, TimelineRecord> _detailRecords = new();
private readonly List<IReadOnlyIssue> _embeddedIssueCollector;
private readonly object _loggerLock = new();
private readonly object _matchersLock = new();
private readonly ExecutionContext _parentExecutionContext;
private event OnMatcherChanged _onMatcherChanged;
@@ -137,6 +134,7 @@ namespace GitHub.Runner.Worker
private IPagingLogger _logger;
private IJobServerQueue _jobServerQueue;
private ExecutionContext _parentExecutionContext;
private Guid _mainTimelineId;
private Guid _detailTimelineId;
@@ -150,29 +148,6 @@ namespace GitHub.Runner.Worker
private long _totalThrottlingDelayInMilliseconds = 0;
private bool _stepTelemetryPublished = false;
public ExecutionContext()
: this(null, false)
{
}
private ExecutionContext(ExecutionContext parent, bool embedded)
{
if (embedded)
{
ArgUtil.NotNull(parent, nameof(parent));
}
_parentExecutionContext = parent;
this.IsEmbedded = embedded;
this.StepTelemetry = new ActionsStepTelemetry
{
IsEmbedded = embedded
};
//Embedded Execution Contexts pseudo-inherit their parent's embeddedIssueCollector.
_embeddedIssueCollector = embedded ? parent._embeddedIssueCollector : new();
}
public Guid Id => _record.Id;
public Guid EmbeddedId { get; private set; }
public string ScopeName { get; private set; }
@@ -185,7 +160,7 @@ namespace GitHub.Runner.Worker
public Dictionary<string, VariableValue> JobOutputs { get; private set; }
public ActionsEnvironmentReference ActionsEnvironment { get; private set; }
public ActionsStepTelemetry StepTelemetry { get; private init; }
public ActionsStepTelemetry StepTelemetry { get; } = new ActionsStepTelemetry();
public DictionaryContextData ExpressionValues { get; } = new DictionaryContextData();
public IList<IFunctionInfo> ExpressionFunctions { get; } = new List<IFunctionInfo>();
@@ -210,7 +185,7 @@ namespace GitHub.Runner.Worker
// An embedded execution context shares the same record ID, record name, and logger
// as its enclosing execution context.
public bool IsEmbedded { get; private init; }
public bool IsEmbedded { get; private set; }
public TaskResult? Result
{
@@ -345,7 +320,7 @@ namespace GitHub.Runner.Worker
{
Trace.Entering();
var child = new ExecutionContext(this, isEmbedded);
var child = new ExecutionContext();
child.Initialize(HostContext);
child.Global = Global;
child.ScopeName = scopeName;
@@ -370,6 +345,7 @@ namespace GitHub.Runner.Worker
child.ExpressionFunctions.Add(item);
}
child._cancellationTokenSource = cancellationTokenSource ?? new CancellationTokenSource();
child._parentExecutionContext = this;
child.EchoOnActionCommand = EchoOnActionCommand;
if (recordOrder != null)
@@ -390,9 +366,11 @@ namespace GitHub.Runner.Worker
child._logger.Setup(_mainTimelineId, recordId);
}
child.IsEmbedded = isEmbedded;
child.StepTelemetry.StepId = recordId;
child.StepTelemetry.Stage = stage.ToString();
child.StepTelemetry.StepContextName = child.GetFullyQualifiedContextName();
child.StepTelemetry.IsEmbedded = isEmbedded;
child.StepTelemetry.StepContextName = child.GetFullyQualifiedContextName(); ;
return child;
}
@@ -434,24 +412,13 @@ namespace GitHub.Runner.Worker
this.Warning($"The job has experienced {TimeSpan.FromMilliseconds(_totalThrottlingDelayInMilliseconds).TotalSeconds} seconds total delay caused by server throttling.");
}
DateTime now = DateTime.UtcNow;
_record.CurrentOperation = currentOperation ?? _record.CurrentOperation;
_record.ResultCode = resultCode ?? _record.ResultCode;
_record.FinishTime = now;
_record.FinishTime = DateTime.UtcNow;
_record.PercentComplete = 100;
_record.Result = _record.Result ?? TaskResult.Succeeded;
_record.State = TimelineRecordState.Completed;
// Before our main timeline's final QueueTimelineRecordUpdate,
// inject any issues collected by embedded ExecutionContexts.
if (!this.IsEmbedded)
{
foreach (var issue in _embeddedIssueCollector)
{
AddIssue(issue);
}
}
_jobServerQueue.QueueTimelineRecordUpdate(_mainTimelineId, _record);
// complete all detail timeline records.
@@ -459,7 +426,7 @@ namespace GitHub.Runner.Worker
{
foreach (var record in _detailRecords)
{
record.Value.FinishTime = record.Value.FinishTime ?? now;
record.Value.FinishTime = record.Value.FinishTime ?? DateTime.UtcNow;
record.Value.PercentComplete = record.Value.PercentComplete ?? 100;
record.Value.Result = record.Value.Result ?? TaskResult.Succeeded;
record.Value.State = TimelineRecordState.Completed;
@@ -579,89 +546,76 @@ namespace GitHub.Runner.Worker
_jobServerQueue.QueueTimelineRecordUpdate(_mainTimelineId, _record);
}
// This is not thread safe, the caller needs to take lock before calling issue()
public IReadOnlyIssue CreateIssue(IssueType issueType, string rawMessage, IssueMetadata metadata, bool writeToLog)
{
string refinedMessage = PrimitiveExtensions.TrimExcess(HostContext.SecretMasker.MaskSecrets(rawMessage), _maxIssueMessageLength);
var result = new Issue() {
Type = issueType,
Message = refinedMessage,
};
if (metadata != null)
{
result.Category = metadata.Category;
result.IsInfrastructureIssue = metadata.IsInfrastructureIssue;
foreach (var kvp in metadata.Data)
{
result[kvp.Key] = kvp.Value;
}
}
// It's important to keep track of the step number (key:stepNumber) and the line number (key:logFileLineNumber) of every issue that gets logged.
// Actions UI from the run summary page use both values to easily link to an exact locations in logs where annotations originate from.
if (_record.Order != null)
{
result["stepNumber"] = _record.Order.ToString();
}
string wellKnownTag = null;
Int32? previousCountForIssueType = null;
switch (issueType)
{
case IssueType.Error:
wellKnownTag = WellKnownTags.Error;
previousCountForIssueType = _record.ErrorCount++;
break;
case IssueType.Warning:
wellKnownTag = WellKnownTags.Warning;
previousCountForIssueType = _record.WarningCount++;
break;
case IssueType.Notice:
wellKnownTag = WellKnownTags.Notice;
previousCountForIssueType = _record.NoticeCount++;
break;
}
if (!string.IsNullOrEmpty(wellKnownTag))
{
if (writeToLog)
{
//Note that ::Write() has it's own secret masking logic
string logText = metadata?.LogMessageOverride ?? result.Message;
if (!string.IsNullOrEmpty(logText))
{
long logLineNumber = Write(wellKnownTag, logText);
result["logFileLineNumber"] = logLineNumber.ToString();
}
}
if (previousCountForIssueType.GetValueOrDefault(0) < _maxIssueCount)
{
_record.Issues.Add(result);
}
}
return result;
}
// This is not thread safe, the caller needs to take lock before calling issue()
public void AddIssue(IReadOnlyIssue issue)
// This is not thread safe, the caller need to take lock before calling issue()
public void AddIssue(Issue issue, string logMessage = null)
{
ArgUtil.NotNull(issue, nameof(issue));
// Embedded ExecutionContexts (a.k.a. Composite actions) should never upload a timeline record to the server.
// Instead, we store processed issues on a shared (psuedo-inherited) list (belonging to the closest
// non-embedded ancestor ExecutionContext) so that they can be processed when that ancestor completes.
if (this.IsEmbedded)
if (string.IsNullOrEmpty(logMessage))
{
_embeddedIssueCollector.Add(issue);
logMessage = issue.Message;
}
else
issue.Message = HostContext.SecretMasker.MaskSecrets(issue.Message);
if (issue.Message.Length > _maxIssueMessageLength)
{
_jobServerQueue.QueueTimelineRecordUpdate(_mainTimelineId, _record);
issue.Message = issue.Message[.._maxIssueMessageLength];
}
// Tracking the line number (logFileLineNumber) and step number (stepNumber) for each issue that gets created
// Actions UI from the run summary page use both values to easily link to an exact locations in logs where annotations originate from
if (_record.Order != null)
{
issue.Data["stepNumber"] = _record.Order.ToString();
}
if (issue.Type == IssueType.Error)
{
if (!string.IsNullOrEmpty(logMessage))
{
long logLineNumber = Write(WellKnownTags.Error, logMessage);
issue.Data["logFileLineNumber"] = logLineNumber.ToString();
}
if (_record.ErrorCount < _maxIssueCount)
{
_record.Issues.Add(issue);
}
_record.ErrorCount++;
}
else if (issue.Type == IssueType.Warning)
{
if (!string.IsNullOrEmpty(logMessage))
{
long logLineNumber = Write(WellKnownTags.Warning, logMessage);
issue.Data["logFileLineNumber"] = logLineNumber.ToString();
}
if (_record.WarningCount < _maxIssueCount)
{
_record.Issues.Add(issue);
}
_record.WarningCount++;
}
else if (issue.Type == IssueType.Notice)
{
if (!string.IsNullOrEmpty(logMessage))
{
long logLineNumber = Write(WellKnownTags.Notice, logMessage);
issue.Data["logFileLineNumber"] = logLineNumber.ToString();
}
if (_record.NoticeCount < _maxIssueCount)
{
_record.Issues.Add(issue);
}
_record.NoticeCount++;
}
_jobServerQueue.QueueTimelineRecordUpdate(_mainTimelineId, _record);
}
public void UpdateDetailTimelineRecord(TimelineRecord record)
@@ -893,6 +847,19 @@ namespace GitHub.Runner.Worker
_jobServerQueue.QueueFileUpload(_mainTimelineId, _record.Id, type, name, filePath, deleteSource: false);
}
public void QueueSummaryFile(string name, string filePath, Guid stepRecordId)
{
ArgUtil.NotNullOrEmpty(name, nameof(name));
ArgUtil.NotNullOrEmpty(filePath, nameof(filePath));
if (!File.Exists(filePath))
{
throw new FileNotFoundException($"Can't upload (name:{name}) file: {filePath}. File does not exist.");
}
_jobServerQueue.QueueSummaryUpload(stepRecordId, name, filePath, deleteSource: false);
}
// Add OnMatcherChanged
public void Add(OnMatcherChanged handler)
{
@@ -1022,7 +989,16 @@ namespace GitHub.Runner.Worker
if ((issue.Type == IssueType.Error || issue.Type == IssueType.Warning) &&
!string.IsNullOrEmpty(issue.Message))
{
string issueTelemetry = PrimitiveExtensions.TrimExcess(issue.Message, _maxIssueMessageLengthInTelemetry);
string issueTelemetry;
if (issue.Message.Length > _maxIssueMessageLengthInTelemetry)
{
issueTelemetry = $"{issue.Message[.._maxIssueMessageLengthInTelemetry]}";
}
else
{
issueTelemetry = issue.Message;
}
StepTelemetry.ErrorMessages.Add(issueTelemetry);
// Only send over the first 3 issues to avoid sending too much data.
@@ -1196,23 +1172,19 @@ namespace GitHub.Runner.Worker
// Do not add a format string overload. See comment on ExecutionContext.Write().
public static void Error(this IExecutionContext context, string message)
{
var issue = context.CreateIssue(IssueType.Error, message, null, true);
context.AddIssue(issue);
context.AddIssue(new Issue() { Type = IssueType.Error, Message = message });
}
// Do not add a format string overload. See comment on ExecutionContext.Write().
public static void InfrastructureError(this IExecutionContext context, string message)
{
var metadata = new IssueMetadata(null, true, null, Enumerable.Empty<KeyValuePair<string, string>>());
var issue = context.CreateIssue(IssueType.Error, message, metadata, true);
context.AddIssue(issue);
context.AddIssue(new Issue() { Type = IssueType.Error, Message = message, IsInfrastructureIssue = true });
}
// Do not add a format string overload. See comment on ExecutionContext.Write().
public static void Warning(this IExecutionContext context, string message)
{
var issue = context.CreateIssue(IssueType.Warning, message, null, true);
context.AddIssue(issue);
context.AddIssue(new Issue() { Type = IssueType.Warning, Message = message });
}
// Do not add a format string overload. See comment on ExecutionContext.Write().

View File

@@ -204,13 +204,23 @@ namespace GitHub.Runner.Worker
}
}
var attachmentName = !context.IsEmbedded
? context.Id.ToString()
var attachmentName = !context.IsEmbedded
? context.Id.ToString()
: context.EmbeddedId.ToString();
Trace.Info($"Queueing file ({filePath}) for attachment upload ({attachmentName})");
// Attachments must be added to the parent context (job), not the current context (step)
context.Root.QueueAttachFile(ChecksAttachmentType.StepSummary, attachmentName, scrubbedFilePath);
// Dual upload the same files to Results Service
context.Global.Variables.TryGetValue("system.github.results_endpoint", out string resultsReceiverEndpoint);
if (resultsReceiverEndpoint != null)
{
Trace.Info($"Queueing results file ({filePath}) for attachment upload ({attachmentName})");
var stepId = context.Id;
// Attachments must be added to the parent context (job), not the current context (step)
context.Root.QueueSummaryFile(attachmentName, scrubbedFilePath, stepId);
}
}
catch (Exception e)
{

View File

@@ -294,7 +294,7 @@ namespace GitHub.Runner.Worker.Handlers
// Evaluation error
Trace.Info("Caught exception from expression for embedded step.env");
step.ExecutionContext.Error(ex);
SetStepConclusion(step, TaskResult.Failed);
step.ExecutionContext.Complete(TaskResult.Failed);
}
// Register Callback

View File

@@ -38,8 +38,17 @@ namespace GitHub.Runner.Worker.Handlers
// Update the env dictionary.
AddInputsToEnvironment();
var dockerManager = HostContext.GetService<IDockerCommandManager>();
var containerHookManager = HostContext.GetService<IContainerHookManager>();
IDockerCommandManager dockerManager = null;
IContainerHookManager containerHookManager = null;
if (FeatureManager.IsContainerHooksEnabled(ExecutionContext.Global.Variables))
{
containerHookManager = HostContext.GetService<IContainerHookManager>();
}
else
{
dockerManager = HostContext.GetService<IDockerCommandManager>();
}
string dockerFile = null;
// container image haven't built/pull

View File

@@ -4,7 +4,6 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common;
using GitHub.Runner.Sdk;
using GitHub.Runner.Worker.Container;
@@ -98,7 +97,7 @@ namespace GitHub.Runner.Worker.Handlers
var matchers = _matchers;
// Strip color codes
var refinedLine = line.Contains(_colorCodePrefix) ? _colorCodeRegex.Replace(line, string.Empty) : line;
var stripped = line.Contains(_colorCodePrefix) ? _colorCodeRegex.Replace(line, string.Empty) : line;
foreach (var matcher in matchers)
{
@@ -108,7 +107,8 @@ namespace GitHub.Runner.Worker.Handlers
// Match
try
{
match = matcher.Match(refinedLine);
match = matcher.Match(stripped);
break;
}
catch (RegexMatchTimeoutException ex)
@@ -116,7 +116,7 @@ namespace GitHub.Runner.Worker.Handlers
if (attempt < _maxAttempts)
{
// Debug
_executionContext.Debug($"Timeout processing issue matcher '{matcher.Owner}' against line '{refinedLine}'. Exception: {ex}");
_executionContext.Debug($"Timeout processing issue matcher '{matcher.Owner}' against line '{stripped}'. Exception: {ex.ToString()}");
}
else
{
@@ -139,10 +139,12 @@ namespace GitHub.Runner.Worker.Handlers
// Convert to issue
var issue = ConvertToIssue(match);
if (issue != null)
{
// Log issue
_executionContext.AddIssue(issue);
_executionContext.AddIssue(issue, stripped);
return;
}
}
@@ -194,7 +196,7 @@ namespace GitHub.Runner.Worker.Handlers
}
}
private DTWebApi.IReadOnlyIssue ConvertToIssue(IssueMatch match)
private DTWebApi.Issue ConvertToIssue(IssueMatch match)
{
// Validate the message
if (string.IsNullOrWhiteSpace(match.Message))
@@ -223,14 +225,18 @@ namespace GitHub.Runner.Worker.Handlers
return null;
}
var issueData = new Dictionary<string, string>();
var issue = new DTWebApi.Issue
{
Message = match.Message,
Type = issueType,
};
// Line
if (!string.IsNullOrEmpty(match.Line))
{
if (int.TryParse(match.Line, NumberStyles.None, CultureInfo.InvariantCulture, out var line))
{
issueData["line"] = line.ToString(CultureInfo.InvariantCulture);
issue.Data["line"] = line.ToString(CultureInfo.InvariantCulture);
}
else
{
@@ -243,7 +249,7 @@ namespace GitHub.Runner.Worker.Handlers
{
if (int.TryParse(match.Column, NumberStyles.None, CultureInfo.InvariantCulture, out var column))
{
issueData["col"] = column.ToString(CultureInfo.InvariantCulture);
issue.Data["col"] = column.ToString(CultureInfo.InvariantCulture);
}
else
{
@@ -254,7 +260,7 @@ namespace GitHub.Runner.Worker.Handlers
// Code
if (!string.IsNullOrWhiteSpace(match.Code))
{
issueData["code"] = match.Code.Trim();
issue.Data["code"] = match.Code.Trim();
}
// File
@@ -306,7 +312,7 @@ namespace GitHub.Runner.Worker.Handlers
var relativePath = file.Substring(repositoryPath.Length).TrimStart(Path.DirectorySeparatorChar);
// Prefer `/` on all platforms
issueData["file"] = relativePath.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
issue.Data["file"] = relativePath.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
}
else
{
@@ -321,11 +327,9 @@ namespace GitHub.Runner.Worker.Handlers
}
catch (Exception ex)
{
_executionContext.Debug($"Dropping file value '{match.File}' and fromPath value '{match.FromPath}'. Exception during validation: {ex}");
_executionContext.Debug($"Dropping file value '{match.File}' and fromPath value '{match.FromPath}'. Exception during validation: {ex.ToString()}");
}
var metadata = new IssueMetadata(null, false, match.SourceText, issueData);
var issue = _executionContext.CreateIssue(issueType, match.Message, metadata, true);
return issue;
}

View File

@@ -8,31 +8,6 @@ namespace GitHub.Runner.Worker
{
public delegate void OnMatcherChanged(object sender, MatcherChangedEventArgs e);
public sealed class IssueMetadata
{
public IssueMetadata(string key, string value)
: this(null, false, null, new []{ KeyValuePair.Create(key, value) })
{
}
public IssueMetadata(string category, bool infrastructureIssue, string logMessageOverride, IEnumerable<KeyValuePair<string, string>> data)
{
this.Category = category;
this.IsInfrastructureIssue = infrastructureIssue;
this.LogMessageOverride = logMessageOverride;
// Close-over the incoming IEnumerable to force immediate evaluation.
var empty = Enumerable.Empty<KeyValuePair<string, string>>();
this.Data = new Dictionary<string, string>(data ?? empty, StringComparer.OrdinalIgnoreCase);
}
public readonly string Category;
public readonly bool IsInfrastructureIssue;
public readonly string LogMessageOverride;
public readonly IEnumerable<KeyValuePair<string, string>> Data;
}
public sealed class MatcherChangedEventArgs : EventArgs
{
public MatcherChangedEventArgs(IssueMatcherConfig config)
@@ -94,7 +69,7 @@ namespace GitHub.Runner.Worker
if (regexMatch.Success)
{
return new IssueMatch(line, null, pattern, regexMatch.Groups, DefaultSeverity);
return new IssueMatch(null, pattern, regexMatch.Groups, DefaultSeverity);
}
return null;
@@ -135,13 +110,13 @@ namespace GitHub.Runner.Worker
}
// Return
return new IssueMatch(line, runningMatch, pattern, regexMatch.Groups, DefaultSeverity);
return new IssueMatch(runningMatch, pattern, regexMatch.Groups, DefaultSeverity);
}
// Not the last pattern
else
{
// Store the match
_state[i] = new IssueMatch(line, runningMatch, pattern, regexMatch.Groups);
_state[i] = new IssueMatch(runningMatch, pattern, regexMatch.Groups);
}
}
// Not matched
@@ -209,9 +184,8 @@ namespace GitHub.Runner.Worker
public sealed class IssueMatch
{
public IssueMatch(string sourceText, IssueMatch runningMatch, IssuePattern pattern, GroupCollection groups, string defaultSeverity = null)
public IssueMatch(IssueMatch runningMatch, IssuePattern pattern, GroupCollection groups, string defaultSeverity = null)
{
SourceText = sourceText;
File = runningMatch?.File ?? GetValue(groups, pattern.File);
Line = runningMatch?.Line ?? GetValue(groups, pattern.Line);
Column = runningMatch?.Column ?? GetValue(groups, pattern.Column);
@@ -226,8 +200,6 @@ namespace GitHub.Runner.Worker
}
}
public string SourceText { get; }
public string File { get; }
public string Line { get; }
@@ -483,7 +455,7 @@ namespace GitHub.Runner.Worker
if (Loop && Message == null)
{
throw new ArgumentException($"The {_loopPropertyName} pattern must set '{_messagePropertyName}'");
}
}
var regex = new Regex(Pattern ?? string.Empty, RegexOptions);
var groupCount = regex.GetGroupNumbers().Length;

View File

@@ -321,25 +321,28 @@ namespace GitHub.Runner.Worker
if (message.Variables.TryGetValue("system.workflowFileFullPath", out VariableValue workflowFileFullPath))
{
context.Output($"Uses: {workflowFileFullPath.Value}");
var usesLogText = $"Uses: {workflowFileFullPath.Value}";
var reference = GetWorkflowReference(message.Variables);
context.Output(usesLogText + reference);
if (message.ContextData.TryGetValue("inputs", out var pipelineContextData))
{
var inputs = pipelineContextData.AssertDictionary("inputs");
if (inputs.Any())
if (inputs.Any())
{
context.Output($"##[group] Inputs");
foreach (var input in inputs)
foreach (var input in inputs)
{
context.Output($" {input.Key}: {input.Value}");
}
context.Output("##[endgroup]");
}
}
}
if (!string.IsNullOrWhiteSpace(message.JobDisplayName))
{
context.Output($"Complete job name: {message.JobDisplayName}");
}
if (!string.IsNullOrWhiteSpace(message.JobDisplayName))
{
context.Output($"Complete job name: {message.JobDisplayName}");
}
var intraActionStates = new Dictionary<Guid, Dictionary<string, string>>();
@@ -452,6 +455,24 @@ namespace GitHub.Runner.Worker
}
}
private string GetWorkflowReference(IDictionary<string, VariableValue> variables)
{
var reference = "";
if (variables.TryGetValue("system.workflowFileSha", out VariableValue workflowFileSha))
{
if (variables.TryGetValue("system.workflowFileRef", out VariableValue workflowFileRef)
&& !string.IsNullOrEmpty(workflowFileRef.Value))
{
reference += $"@{workflowFileRef.Value} ({workflowFileSha.Value})";
}
else
{
reference += $"@{workflowFileSha.Value}";
}
}
return reference;
}
public void FinalizeJob(IExecutionContext jobContext, Pipelines.AgentJobRequestMessage message, DateTime jobStartTimeUtc)
{
Trace.Entering();
@@ -660,9 +681,8 @@ namespace GitHub.Runner.Worker
var freeSpaceInMB = driveInfo.AvailableFreeSpace / 1024 / 1024;
if (freeSpaceInMB < lowDiskSpaceThreshold)
{
var message = $"You are running out of disk space. The runner will stop working when the machine runs out of disk space. Free space left: {freeSpaceInMB} MB";
var metadata = new IssueMetadata(Constants.Runner.InternalTelemetryIssueDataKey, Constants.Runner.LowDiskSpace);
var issue = context.CreateIssue(IssueType.Warning, message, metadata, true);
var issue = new Issue() { Type = IssueType.Warning, Message = $"You are running out of disk space. The runner will stop working when the machine runs out of disk space. Free space left: {freeSpaceInMB} MB" };
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.LowDiskSpace;
context.AddIssue(issue);
return;
}

View File

@@ -84,8 +84,7 @@ namespace GitHub.Runner.Worker
default:
throw new ArgumentException(HostContext.RunnerShutdownReason.ToString(), nameof(HostContext.RunnerShutdownReason));
}
var issue = jobContext.CreateIssue(IssueType.Error, errorMessage, null, true);
jobContext.AddIssue(issue);
jobContext.AddIssue(new Issue() { Type = IssueType.Error, Message = errorMessage });
});
// Validate directory permissions.

View File

@@ -27,18 +27,6 @@ namespace GitHub.Services.Common
}
}
public static string TrimExcess(string text, int maxLength)
{
string result = text;
if (!string.IsNullOrEmpty(result) && result.Length > maxLength)
{
result = result.Substring(0, maxLength);
}
return result;
}
public static string ToBase64StringNoPaddingFromString(string utf8String)
{
return ToBase64StringNoPadding(Encoding.UTF8.GetBytes(utf8String));
@@ -58,7 +46,7 @@ namespace GitHub.Services.Common
//These methods convert To and From base64 strings without padding
//for JWT scenarios
//code taken from the JWS spec here:
//code taken from the JWS spec here:
//http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-08#appendix-C
public static String ToBase64StringNoPadding(this byte[] bytes)
{

View File

@@ -1,38 +1,30 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using GitHub.Services.Common;
namespace GitHub.DistributedTask.WebApi
{
public interface IReadOnlyIssue
{
IssueType Type { get; }
string Category { get; }
string Message { get; }
bool? IsInfrastructureIssue { get; }
string this[string key] { get; }
}
[DataContract]
public class Issue : IReadOnlyIssue
public class Issue
{
public Issue()
: this(null)
{
}
private Issue(Issue original)
private Issue(Issue issueToBeCloned)
{
this.EnsureInitialized();
if (original != null)
this.Type = issueToBeCloned.Type;
this.Category = issueToBeCloned.Category;
this.Message = issueToBeCloned.Message;
this.IsInfrastructureIssue = issueToBeCloned.IsInfrastructureIssue;
if (issueToBeCloned.m_data != null)
{
this.Type = original.Type;
this.Category = original.Category;
this.Message = original.Message;
this.IsInfrastructureIssue = original.IsInfrastructureIssue;
m_data.AddRange(original.m_data);
foreach (var item in issueToBeCloned.m_data)
{
this.Data.Add(item);
}
}
}
@@ -44,14 +36,14 @@ namespace GitHub.DistributedTask.WebApi
}
[DataMember(Order = 2)]
public string Category
public String Category
{
get;
set;
}
[DataMember(Order = 3)]
public string Message
public String Message
{
get;
set;
@@ -64,16 +56,15 @@ namespace GitHub.DistributedTask.WebApi
set;
}
public string this[string key]
public IDictionary<String, String> Data
{
get
{
m_data.TryGetValue(key, out string result);
return result;
}
set
{
m_data[key] = value;
if (m_data == null)
{
m_data = new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);
}
return m_data;
}
}
@@ -86,7 +77,6 @@ namespace GitHub.DistributedTask.WebApi
private void OnDeserialized(StreamingContext context)
{
SerializationHelper.Copy(ref m_serializedData, ref m_data, StringComparer.OrdinalIgnoreCase, true);
this.EnsureInitialized();
}
[OnSerializing]
@@ -101,21 +91,9 @@ namespace GitHub.DistributedTask.WebApi
m_serializedData = null;
}
/// <summary>
/// DataContractSerializer bypasses all constructor logic and inline initialization!
/// This method takes the place of a workhorse constructor for baseline initialization.
/// The expectation is for this logic to be accessible to constructors and also to the OnDeserialized helper.
/// </summary>
private void EnsureInitialized()
{
//Note that ?? is a short-circuiting operator.
m_data = m_data ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
[DataMember(Name = "Data", EmitDefaultValue = false, Order = 4)]
private IDictionary<string, string> m_serializedData;
private IDictionary<string, string> m_data;
private IDictionary<String, String> m_serializedData;
private IDictionary<String, String> m_data;
}
}

View File

@@ -27,7 +27,6 @@ namespace GitHub.DistributedTask.WebApi
this.Type = type;
this.Name = name;
}
[DataMember]
public String Type

View File

@@ -10,76 +10,69 @@ namespace GitHub.DistributedTask.WebApi
public sealed class TimelineRecord
{
public TimelineRecord()
: this(null)
{
this.Attempt = 1;
}
private TimelineRecord(TimelineRecord recordToBeCloned)
{
this.EnsureInitialized();
if (recordToBeCloned != null)
this.Attempt = recordToBeCloned.Attempt;
this.ChangeId = recordToBeCloned.ChangeId;
this.CurrentOperation = recordToBeCloned.CurrentOperation;
this.FinishTime = recordToBeCloned.FinishTime;
this.Id = recordToBeCloned.Id;
this.Identifier = recordToBeCloned.Identifier;
this.LastModified = recordToBeCloned.LastModified;
this.Location = recordToBeCloned.Location;
this.Name = recordToBeCloned.Name;
this.Order = recordToBeCloned.Order;
this.ParentId = recordToBeCloned.ParentId;
this.PercentComplete = recordToBeCloned.PercentComplete;
this.RecordType = recordToBeCloned.RecordType;
this.Result = recordToBeCloned.Result;
this.ResultCode = recordToBeCloned.ResultCode;
this.StartTime = recordToBeCloned.StartTime;
this.State = recordToBeCloned.State;
this.TimelineId = recordToBeCloned.TimelineId;
this.WorkerName = recordToBeCloned.WorkerName;
this.RefName = recordToBeCloned.RefName;
this.ErrorCount = recordToBeCloned.ErrorCount;
this.WarningCount = recordToBeCloned.WarningCount;
this.NoticeCount = recordToBeCloned.NoticeCount;
this.AgentPlatform = recordToBeCloned.AgentPlatform;
if (recordToBeCloned.Log != null)
{
this.Attempt = recordToBeCloned.Attempt;
this.ChangeId = recordToBeCloned.ChangeId;
this.CurrentOperation = recordToBeCloned.CurrentOperation;
this.FinishTime = recordToBeCloned.FinishTime;
this.Id = recordToBeCloned.Id;
this.Identifier = recordToBeCloned.Identifier;
this.LastModified = recordToBeCloned.LastModified;
this.Location = recordToBeCloned.Location;
this.Name = recordToBeCloned.Name;
this.Order = recordToBeCloned.Order;
this.ParentId = recordToBeCloned.ParentId;
this.PercentComplete = recordToBeCloned.PercentComplete;
this.RecordType = recordToBeCloned.RecordType;
this.Result = recordToBeCloned.Result;
this.ResultCode = recordToBeCloned.ResultCode;
this.StartTime = recordToBeCloned.StartTime;
this.State = recordToBeCloned.State;
this.TimelineId = recordToBeCloned.TimelineId;
this.WorkerName = recordToBeCloned.WorkerName;
this.RefName = recordToBeCloned.RefName;
this.ErrorCount = recordToBeCloned.ErrorCount;
this.WarningCount = recordToBeCloned.WarningCount;
this.NoticeCount = recordToBeCloned.NoticeCount;
this.AgentPlatform = recordToBeCloned.AgentPlatform;
if (recordToBeCloned.Log != null)
this.Log = new TaskLogReference
{
this.Log = new TaskLogReference
{
Id = recordToBeCloned.Log.Id,
Location = recordToBeCloned.Log.Location,
};
}
Id = recordToBeCloned.Log.Id,
Location = recordToBeCloned.Log.Location,
};
}
if (recordToBeCloned.Details != null)
if (recordToBeCloned.Details != null)
{
this.Details = new TimelineReference
{
this.Details = new TimelineReference
{
ChangeId = recordToBeCloned.Details.ChangeId,
Id = recordToBeCloned.Details.Id,
Location = recordToBeCloned.Details.Location,
};
}
ChangeId = recordToBeCloned.Details.ChangeId,
Id = recordToBeCloned.Details.Id,
Location = recordToBeCloned.Details.Location,
};
}
if (recordToBeCloned.m_issues?.Count > 0)
{
m_issues.AddRange(recordToBeCloned.m_issues.Select(i => i.Clone()));
}
if (recordToBeCloned.m_issues?.Count> 0)
{
this.Issues.AddRange(recordToBeCloned.Issues.Select(i => i.Clone()));
}
if (recordToBeCloned.m_previousAttempts?.Count > 0)
{
m_previousAttempts.AddRange(recordToBeCloned.m_previousAttempts);
}
if (recordToBeCloned.m_previousAttempts?.Count > 0)
{
this.PreviousAttempts.AddRange(recordToBeCloned.PreviousAttempts);
}
if (recordToBeCloned.m_variables?.Count > 0)
{
// Don't pave over the case-insensitive Dictionary we initialized above.
foreach (var kvp in recordToBeCloned.m_variables) {
m_variables[kvp.Key] = kvp.Value.Clone();
}
}
if (recordToBeCloned.m_variables?.Count > 0)
{
this.m_variables = recordToBeCloned.Variables.ToDictionary(k => k.Key, v => v.Value.Clone());
}
}
@@ -241,6 +234,10 @@ namespace GitHub.DistributedTask.WebApi
{
get
{
if (m_issues == null)
{
m_issues = new List<Issue>();
}
return m_issues;
}
}
@@ -277,14 +274,22 @@ namespace GitHub.DistributedTask.WebApi
{
get
{
if (m_previousAttempts == null)
{
m_previousAttempts = new List<TimelineAttempt>();
}
return m_previousAttempts;
}
}
public IDictionary<string, VariableValue> Variables
public IDictionary<String, VariableValue> Variables
{
get
{
if (m_variables == null)
{
m_variables = new Dictionary<String, VariableValue>(StringComparer.OrdinalIgnoreCase);
}
return m_variables;
}
}
@@ -294,32 +299,11 @@ namespace GitHub.DistributedTask.WebApi
return new TimelineRecord(this);
}
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
this.EnsureInitialized();
}
/// <summary>
/// DataContractSerializer bypasses all constructor logic and inline initialization!
/// This method takes the place of a workhorse constructor for baseline initialization.
/// The expectation is for this logic to be accessible to constructors and also to the OnDeserialized helper.
/// </summary>
private void EnsureInitialized()
{
//Note that ?? is a short-circuiting operator.
m_issues = m_issues ?? new List<Issue>();
m_variables = m_variables ?? new Dictionary<string, VariableValue>(StringComparer.OrdinalIgnoreCase);
m_previousAttempts = m_previousAttempts ?? new List<TimelineAttempt>();
this.Attempt = Math.Max(this.Attempt, 1);
}
[DataMember(Name = "Issues", EmitDefaultValue = false, Order = 60)]
private List<Issue> m_issues;
[DataMember(Name = "Variables", EmitDefaultValue = false, Order = 80)]
private Dictionary<string, VariableValue> m_variables;
private Dictionary<String, VariableValue> m_variables;
[DataMember(Name = "PreviousAttempts", EmitDefaultValue = false, Order = 120)]
private List<TimelineAttempt> m_previousAttempts;

View File

@@ -0,0 +1,60 @@
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace GitHub.Services.Results.Contracts
{
[DataContract]
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
public class GetSignedStepSummaryURLRequest
{
[DataMember]
public string WorkflowJobRunBackendId;
[DataMember]
public string WorkflowRunBackendId;
[DataMember]
public string StepBackendId;
}
[DataContract]
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
public class GetSignedStepSummaryURLResponse
{
[DataMember]
public string SummaryUrl;
[DataMember]
public long SoftSizeLimit;
[DataMember]
public string BlobStorageType;
}
[DataContract]
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
public class StepSummaryMetadataCreate
{
[DataMember]
public string StepBackendId;
[DataMember]
public string WorkflowRunBackendId;
[DataMember]
public string WorkflowJobRunBackendId;
[DataMember]
public long Size;
[DataMember]
public string UploadedAt;
}
[DataContract]
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
public class CreateStepSummaryMetadataResponse
{
[DataMember]
public bool Ok;
}
public static class BlobStorageTypes
{
public static readonly string AzureBlobStorage = "BLOB_STORAGE_TYPE_AZURE";
public static readonly string Unspecified = "BLOB_STORAGE_TYPE_UNSPECIFIED";
}
}

View File

@@ -0,0 +1,142 @@
using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using GitHub.Services.Results.Contracts;
using System.Net.Http.Formatting;
using Sdk.WebApi.WebApi;
namespace GitHub.Services.Results.Client
{
public class ResultsHttpClient : RawHttpClientBase
{
public ResultsHttpClient(
Uri baseUrl,
HttpMessageHandler pipeline,
string token,
bool disposeHandler)
: base(baseUrl, pipeline, disposeHandler)
{
m_token = token;
m_resultsServiceUrl = baseUrl;
m_formatter = new JsonMediaTypeFormatter();
}
public async Task<GetSignedStepSummaryURLResponse> GetStepSummaryUploadUrlAsync(string planId, string jobId, string stepId, CancellationToken cancellationToken)
{
var request = new GetSignedStepSummaryURLRequest()
{
WorkflowJobRunBackendId= jobId,
WorkflowRunBackendId= planId,
StepBackendId= stepId
};
var stepSummaryUploadRequest = new Uri(m_resultsServiceUrl, "twirp/results.services.receiver.Receiver/GetStepSummarySignedBlobURL");
using (HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, stepSummaryUploadRequest))
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", m_token);
requestMessage.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
using (HttpContent content = new ObjectContent<GetSignedStepSummaryURLRequest>(request, m_formatter))
{
requestMessage.Content = content;
using (var response = await SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationToken: cancellationToken))
{
return await ReadJsonContentAsync<GetSignedStepSummaryURLResponse>(response, cancellationToken);
}
}
}
}
private async Task StepSummaryUploadCompleteAsync(string planId, string jobId, string stepId, long size, CancellationToken cancellationToken)
{
var timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd'T'HH:mm:ss.fffK");
var request = new StepSummaryMetadataCreate()
{
WorkflowJobRunBackendId= jobId,
WorkflowRunBackendId= planId,
StepBackendId = stepId,
Size = size,
UploadedAt = timestamp
};
var stepSummaryUploadCompleteRequest = new Uri(m_resultsServiceUrl, "twirp/results.services.receiver.Receiver/CreateStepSummaryMetadata");
using (HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, stepSummaryUploadCompleteRequest))
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", m_token);
requestMessage.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
using (HttpContent content = new ObjectContent<StepSummaryMetadataCreate>(request, m_formatter))
{
requestMessage.Content = content;
using (var response = await SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationToken: cancellationToken))
{
var jsonResponse = await ReadJsonContentAsync<CreateStepSummaryMetadataResponse>(response, cancellationToken);
if (!jsonResponse.Ok)
{
throw new Exception($"Failed to mark step summary upload as complete, status code: {response.StatusCode}, ok: {jsonResponse.Ok}, size: {size}, timestamp: {timestamp}");
}
}
}
}
}
private async Task<HttpResponseMessage> UploadFileAsync(string url, string blobStorageType, FileStream file, CancellationToken cancellationToken)
{
// Upload the file to the url
var request = new HttpRequestMessage(HttpMethod.Put, url)
{
Content = new StreamContent(file)
};
if (blobStorageType == BlobStorageTypes.AzureBlobStorage)
{
request.Content.Headers.Add("x-ms-blob-type", "BlockBlob");
}
using (var response = await SendAsync(request, HttpCompletionOption.ResponseHeadersRead, userState: null, cancellationToken))
{
if (!response.IsSuccessStatusCode)
{
throw new Exception($"Failed to upload file, status code: {response.StatusCode}, reason: {response.ReasonPhrase}");
}
return response;
}
}
// Handle file upload for step summary
public async Task UploadStepSummaryAsync(string planId, string jobId, string stepId, string file, CancellationToken cancellationToken)
{
// Get the upload url
var uploadUrlResponse = await GetStepSummaryUploadUrlAsync(planId, jobId, stepId, cancellationToken);
if (uploadUrlResponse == null)
{
throw new Exception("Failed to get step summary upload url");
}
// Do we want to throw an exception here or should we just be uploading/truncating the data
var fileSize = new FileInfo(file).Length;
if (fileSize > uploadUrlResponse.SoftSizeLimit)
{
throw new Exception($"File size is larger than the upload url allows, file size: {fileSize}, upload url size: {uploadUrlResponse.SoftSizeLimit}");
}
// Upload the file
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
{
var response = await UploadFileAsync(uploadUrlResponse.SummaryUrl, uploadUrlResponse.BlobStorageType, fileStream, cancellationToken);
}
// Send step summary upload complete message
await StepSummaryUploadCompleteAsync(planId, jobId, stepId, fileSize, cancellationToken);
}
private MediaTypeFormatter m_formatter;
private Uri m_resultsServiceUrl;
private string m_token;
}
}

View File

@@ -824,6 +824,7 @@ namespace GitHub.Runner.Common.Tests
[InlineData("remove", "version")]
[InlineData("remove", "commit")]
[InlineData("remove", "check")]
[InlineData("remove", "local")]
[InlineData("run", "help")]
[InlineData("run", "version")]
[InlineData("run", "commit")]

View File

@@ -502,5 +502,34 @@ namespace GitHub.Runner.Common.Tests.Listener
_messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.Once());
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Runner")]
public async void TestRemoveLocalRunnerConfig()
{
using (var hc = new TestHostContext(this))
{
hc.SetSingleton<IConfigurationManager>(_configurationManager.Object);
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
hc.SetSingleton<IPromptManager>(_promptManager.Object);
var command = new CommandSettings(hc, new[] { "remove", "--local" });
_configStore.Setup(x => x.IsConfigured())
.Returns(true);
_configStore.Setup(x => x.HasCredentials())
.Returns(true);
var runner = new Runner.Listener.Runner();
runner.Initialize(hc);
await runner.ExecuteCommand(command);
// verify that we delete the local runner config with the correct remove parameter
_configurationManager.Verify(x => x.DeleteLocalRunnerConfig(), Times.Once());
}
}
}
}

View File

@@ -128,7 +128,76 @@ namespace GitHub.Runner.Common.Tests
}
}
}
#if OS_WINDOWS
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public async Task SetTestEnvWithNullInKey()
{
using (TestHostContext hc = new(this))
{
Tracing trace = hc.GetTrace();
Int32 exitCode = -1;
var processInvoker = new ProcessInvokerWrapper();
processInvoker.Initialize(hc);
var stdout = new List<string>();
var stderr = new List<string>();
processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs e) =>
{
trace.Info(e.Data);
stdout.Add(e.Data);
};
processInvoker.ErrorDataReceived += (object sender, ProcessDataReceivedEventArgs e) =>
{
trace.Info(e.Data);
stderr.Add(e.Data);
};
exitCode = await processInvoker.ExecuteAsync("", "cmd.exe", "/c \"echo %TEST%\"", new Dictionary<string, string>() { { "TEST\0second", "first" } }, CancellationToken.None);
trace.Info("Exit Code: {0}", exitCode);
Assert.Equal(0, exitCode);
Assert.Equal("first", stdout.First(x => !string.IsNullOrWhiteSpace(x)));
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public async Task SetTestEnvWithNullInValue()
{
using (TestHostContext hc = new(this))
{
Tracing trace = hc.GetTrace();
Int32 exitCode = -1;
var processInvoker = new ProcessInvokerWrapper();
processInvoker.Initialize(hc);
var stdout = new List<string>();
var stderr = new List<string>();
processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs e) =>
{
trace.Info(e.Data);
stdout.Add(e.Data);
};
processInvoker.ErrorDataReceived += (object sender, ProcessDataReceivedEventArgs e) =>
{
trace.Info(e.Data);
stderr.Add(e.Data);
};
exitCode = await processInvoker.ExecuteAsync("", "cmd.exe", "/c \"echo %TEST%\"", new Dictionary<string, string>() { { "TEST", "first\0second" } }, CancellationToken.None);
trace.Info("Exit Code: {0}", exitCode);
Assert.Equal(0, exitCode);
Assert.Equal("first", stdout.First(x => !string.IsNullOrWhiteSpace(x)));
}
}
#endif
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]

View File

@@ -2,8 +2,6 @@
using Xunit;
using GitHub.Runner.Sdk;
using System.Runtime.CompilerServices;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Worker;
namespace GitHub.Runner.Common.Tests
{
@@ -43,26 +41,5 @@ namespace GitHub.Runner.Common.Tests
Assert.True(Directory.Exists(testDataDir));
return testDataDir;
}
public static IReadOnlyIssue CreateTestIssue(IssueType type, string message, IssueMetadata metadata, bool writeToLog)
{
var result = new Issue()
{
Type = type,
Message = message,
};
if (metadata != null)
{
result.Category = metadata.Category;
result.IsInfrastructureIssue = metadata.IsInfrastructureIssue;
foreach (var kvp in metadata.Data)
{
result[kvp.Key] = kvp.Value;
}
}
return result;
}
}
}

View File

@@ -32,10 +32,10 @@ namespace GitHub.Runner.Common.Tests.Worker
hc.GetTrace().Info($"{tag} {line}");
return 1;
});
_ec.Setup(x => x.AddIssue(It.IsAny<IReadOnlyIssue>()))
.Callback((IReadOnlyIssue issue) =>
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>()))
.Callback((Issue issue, string message) =>
{
hc.GetTrace().Info($"{issue.Type} {issue.Message}");
hc.GetTrace().Info($"{issue.Type} {issue.Message} {message ?? string.Empty}");
});
_commandManager.EnablePluginInternalCommand();
@@ -59,10 +59,10 @@ namespace GitHub.Runner.Common.Tests.Worker
hc.GetTrace().Info($"{tag} {line}");
return 1;
});
_ec.Setup(x => x.AddIssue(It.IsAny<IReadOnlyIssue>()))
.Callback((IReadOnlyIssue issue) =>
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>()))
.Callback((Issue issue, string message) =>
{
hc.GetTrace().Info($"{issue.Type} {issue.Message}");
hc.GetTrace().Info($"{issue.Type} {issue.Message} {message ?? string.Empty}");
});
_commandManager.EnablePluginInternalCommand();
@@ -92,12 +92,10 @@ namespace GitHub.Runner.Common.Tests.Worker
return 1;
});
_ec.Setup(x => x.CreateIssue(It.IsAny<IssueType>(), It.IsAny<string>(), It.IsAny<IssueMetadata>(), It.IsAny<bool>()))
.Returns(TestUtil.CreateTestIssue);
_ec.Setup(x => x.AddIssue(It.IsAny<IReadOnlyIssue>()))
.Callback((IReadOnlyIssue issue) =>
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>()))
.Callback((Issue issue, string message) =>
{
hc.GetTrace().Info($"{issue.Type} {issue.Message}");
hc.GetTrace().Info($"{issue.Type} {issue.Message} {message ?? string.Empty}");
});
_ec.Object.Global.EnvironmentVariables = new Dictionary<string, string>();

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Http;
using System.Runtime.CompilerServices;
@@ -13,11 +14,6 @@ using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Sdk;
using GitHub.Runner.Worker;
using GitHub.Runner.Worker.Container;
#if OS_WINDOWS
using System.IO.Compression;
#endif
using Moq;
using Moq.Protected;
using Xunit;
@@ -2151,9 +2147,7 @@ runs:
_ec.Object.Global.FileTable = new List<String>();
_ec.Object.Global.Plan = new TaskOrchestrationPlanReference();
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"[{tag}]{message}"); });
_ec.Setup(x => x.CreateIssue(It.IsAny<IssueType>(), It.IsAny<string>(), It.IsAny<IssueMetadata>(), It.IsAny<bool>()))
.Returns(TestUtil.CreateTestIssue);
_ec.Setup(x => x.AddIssue(It.IsAny<IReadOnlyIssue>())).Callback((IReadOnlyIssue issue) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message}"); });
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); });
_ec.Setup(x => x.GetGitHubContext("workspace")).Returns(Path.Combine(_workFolder, "actions", "actions"));
_dockerManager = new Mock<IDockerCommandManager>();

View File

@@ -4,10 +4,10 @@ using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Worker;
using GitHub.Runner.Worker.Expressions;
using GitHub.Services.Common;
using Moq;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
@@ -670,7 +670,7 @@ namespace GitHub.Runner.Common.Tests.Worker
{
Teardown();
}
}
}
[Fact]
[Trait("Level", "L0")]
@@ -715,7 +715,7 @@ namespace GitHub.Runner.Common.Tests.Worker
//Assert
var err = Assert.Throws<ArgumentException>(() => actionManifest.Load(_ec.Object, action_path));
Assert.Contains($"Fail to load {action_path}", err.Message);
_ec.Verify(x => x.AddIssue(It.Is<IReadOnlyIssue>(s => s.Message.Contains("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12' or 'node16'."))), Times.Once);
_ec.Verify(x => x.AddIssue(It.Is<Issue>(s => s.Message.Contains("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12' or 'node16'.")), It.IsAny<string>()), Times.Once);
}
finally
{
@@ -860,10 +860,7 @@ namespace GitHub.Runner.Common.Tests.Worker
_ec.Setup(x => x.ExpressionValues).Returns(new DictionaryContextData());
_ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"{tag}{message}"); });
_ec.Setup(x => x.CreateIssue(It.IsAny<IssueType>(), It.IsAny<string>(), It.IsAny<IssueMetadata>(), It.IsAny<bool>()))
.Returns(TestUtil.CreateTestIssue);
_ec.Setup(x => x.AddIssue(It.IsAny<IReadOnlyIssue>())).Callback((IReadOnlyIssue issue) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message}"); });
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); });
}
private void Teardown()

View File

@@ -3,15 +3,20 @@ using GitHub.DistributedTask.ObjectTemplating.Tokens;
using GitHub.DistributedTask.Pipelines;
using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Worker;
using GitHub.Runner.Worker.Container;
using GitHub.Runner.Worker.Handlers;
using GitHub.Services.Common;
using Moq;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
using Pipelines = GitHub.DistributedTask.Pipelines;
@@ -325,7 +330,7 @@ namespace GitHub.Runner.Common.Tests.Worker
Assert.Equal("invalid1", finialInputs["invalid1"]);
Assert.Equal("invalid2", finialInputs["invalid2"]);
_ec.Verify(x => x.AddIssue(It.Is<IReadOnlyIssue>(s => s.Message.Contains("Unexpected input(s) 'invalid1', 'invalid2'"))), Times.Once);
_ec.Verify(x => x.AddIssue(It.Is<Issue>(s => s.Message.Contains("Unexpected input(s) 'invalid1', 'invalid2'")), It.IsAny<string>()), Times.Once);
}
[Fact]
@@ -444,9 +449,7 @@ namespace GitHub.Runner.Common.Tests.Worker
_ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token);
_ec.Object.Global.Variables = new Variables(_hc, new Dictionary<string, VariableValue>());
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"[{tag}]{message}"); });
_ec.Setup(x => x.CreateIssue(It.IsAny<IssueType>(), It.IsAny<string>(), It.IsAny<IssueMetadata>(), It.IsAny<bool>()))
.Returns(TestUtil.CreateTestIssue);
_ec.Setup(x => x.AddIssue(It.IsAny<IReadOnlyIssue>())).Callback((IReadOnlyIssue issue) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message}"); });
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); });
_hc.SetSingleton<IActionManager>(_actionManager.Object);
_hc.SetSingleton<IHandlerFactory>(_handlerFactory.Object);

View File

@@ -99,6 +99,46 @@ namespace GitHub.Runner.Common.Tests.Worker
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void InitializeWithCorrectManager()
{
containers.Add(new ContainerInfo() { ContainerImage = "ubuntu:16.04" });
_hc = new TestHostContext(this, "Test");
_ec = new Mock<IExecutionContext>();
serverQueue = new Mock<IJobServerQueue>();
pagingLogger = new Mock<IPagingLogger>();
containerOperationProvider = new ContainerOperationProvider();
_hc.SetSingleton<IJobServerQueue>(serverQueue.Object);
_hc.SetSingleton<IPagingLogger>(pagingLogger.Object);
_ec.Setup(x => x.Global).Returns(new GlobalContext());
Environment.SetEnvironmentVariable(Constants.Hooks.ContainerHooksPath, "/tmp/k8s/index.js");
_dockerManager = new Mock<IDockerCommandManager>();
_dockerManager.Setup(x => x.Initialize(_hc)).Throws(new Exception("Docker manager's Initialize should not be called"));
_containerHookManager = new Mock<IContainerHookManager>();
_hc.SetSingleton<IDockerCommandManager>(_dockerManager.Object);
_hc.SetSingleton<IContainerHookManager>(_containerHookManager.Object);
containerOperationProvider.Initialize(_hc);
Environment.SetEnvironmentVariable(Constants.Hooks.ContainerHooksPath, null);
_containerHookManager = new Mock<IContainerHookManager>();
_containerHookManager.Setup(x => x.Initialize(_hc)).Throws(new Exception("Container hook manager's Initialize should not be called"));
_dockerManager = new Mock<IDockerCommandManager>();
_hc.SetSingleton<IDockerCommandManager>(_dockerManager.Object);
_hc.SetSingleton<IContainerHookManager>(_containerHookManager.Object);
containerOperationProvider.Initialize(_hc);
}
private void Setup([CallerMemberName] string testName = "")
{
containers.Add(new ContainerInfo() { ContainerImage = "ubuntu:16.04" });
@@ -111,7 +151,6 @@ namespace GitHub.Runner.Common.Tests.Worker
_containerHookManager = new Mock<IContainerHookManager>();
containerOperationProvider = new ContainerOperationProvider();
_hc.SetSingleton<IDockerCommandManager>(_dockerManager.Object);
_hc.SetSingleton<IJobServerQueue>(serverQueue.Object);
_hc.SetSingleton<IPagingLogger>(pagingLogger.Object);

View File

@@ -11,7 +11,6 @@ using Xunit;
using DTWebApi = GitHub.DistributedTask.WebApi;
using GitHub.DistributedTask.WebApi;
using Pipelines = GitHub.DistributedTask.Pipelines;
using GitHub.Services.Common;
namespace GitHub.Runner.Common.Tests.Worker
{
@@ -20,7 +19,7 @@ namespace GitHub.Runner.Common.Tests.Worker
private Mock<IExecutionContext> _executionContext;
private Mock<IJobServerQueue> _jobServerQueue;
private ExecutionContext _jobExecutionContext;
private List<DTWebApi.IReadOnlyIssue> _issues;
private List<Tuple<DTWebApi.Issue, string>> _issues;
private Variables _variables;
private string _rootDirectory;
private CreateStepSummaryCommand _createStepCommand;
@@ -187,7 +186,7 @@ namespace GitHub.Runner.Common.Tests.Worker
{
var hostContext = new TestHostContext(this, name);
_issues = new List<DTWebApi.IReadOnlyIssue>();
_issues = new List<Tuple<DTWebApi.Issue, string>>();
// Setup a job request
TaskOrchestrationPlanReference plan = new();
@@ -248,14 +247,13 @@ namespace GitHub.Runner.Common.Tests.Worker
WriteDebug = true,
Variables = _variables,
});
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.IReadOnlyIssue>()))
.Callback((DTWebApi.IReadOnlyIssue issue) =>
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<string>()))
.Callback((DTWebApi.Issue issue, string logMessage) =>
{
_issues.Add(issue);
_trace.Info($"Issue '{issue.Type}': {issue.Message}");
_issues.Add(new Tuple<DTWebApi.Issue, string>(issue, logMessage));
var message = !string.IsNullOrEmpty(logMessage) ? logMessage : issue.Message;
_trace.Info($"Issue '{issue.Type}': {message}");
});
_executionContext.Setup(x => x.CreateIssue(It.IsAny<IssueType>(), It.IsAny<string>(), It.IsAny<IssueMetadata>(), It.IsAny<bool>()))
.Returns(TestUtil.CreateTestIssue);
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
.Callback((string tag, string message) =>
{

View File

@@ -52,36 +52,36 @@ namespace GitHub.Runner.Common.Tests.Worker
// Act.
ec.InitializeJob(jobRequest, CancellationToken.None);
ec.AddIssue(ec.CreateIssue(IssueType.Error, "error", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Error, "error", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Error, "error", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Error, "error", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Error, "error", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Error, "error", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Error, "error", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Error, "error", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Error, "error", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Error, "error", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Error, "error", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Error, "error", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Error, "error", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Error, "error", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Error, "error", null, true));
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(ec.CreateIssue(IssueType.Warning, "warning", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Warning, "warning", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Warning, "warning", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Warning, "warning", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Warning, "warning", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Warning, "warning", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Warning, "warning", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Warning, "warning", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Warning, "warning", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Warning, "warning", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Warning, "warning", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Warning, "warning", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Warning, "warning", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Warning, "warning", null, true));
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.Complete();
@@ -190,9 +190,9 @@ namespace GitHub.Runner.Common.Tests.Worker
bigMessage += "a";
}
ec.AddIssue(ec.CreateIssue(IssueType.Error, bigMessage, null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Warning, bigMessage, null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Notice, bigMessage, null, true));
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = bigMessage });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = bigMessage });
ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = bigMessage });
ec.Complete();
@@ -242,13 +242,13 @@ namespace GitHub.Runner.Common.Tests.Worker
var embeddedStep = ec.CreateChild(Guid.NewGuid(), "action_1_pre", "action_1_pre", null, null, ActionRunStage.Main, isEmbedded: true);
embeddedStep.Start();
embeddedStep.AddIssue(embeddedStep.CreateIssue(IssueType.Error, "error annotation that should have step and line number information", null, true));
embeddedStep.AddIssue(embeddedStep.CreateIssue(IssueType.Warning, "warning annotation that should have step and line number information", null, true));
embeddedStep.AddIssue(embeddedStep.CreateIssue(IssueType.Notice, "notice annotation that should have step and line number information", null, true));
embeddedStep.AddIssue(new Issue() { Type = IssueType.Error, Message = "error annotation that should have step and line number information" });
embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning annotation that should have step and line number information" });
embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice annotation that should have step and line number information" });
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i["stepNumber"] != null && i["logFileLineNumber"] != null && i.Type == IssueType.Error).Count() == 1)), Times.AtLeastOnce);
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i["stepNumber"] != null && i["logFileLineNumber"] != null && i.Type == IssueType.Warning).Count() == 1)), Times.AtLeastOnce);
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i["stepNumber"] != null && i["logFileLineNumber"] != null && i.Type == IssueType.Notice).Count() == 1)), Times.AtLeastOnce);
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Data.ContainsKey("stepNumber") && i.Data.ContainsKey("logFileLineNumber") && i.Type == IssueType.Error).Count() == 1)), Times.AtLeastOnce);
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Data.ContainsKey("stepNumber") && i.Data.ContainsKey("logFileLineNumber") && i.Type == IssueType.Warning).Count() == 1)), Times.AtLeastOnce);
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Data.ContainsKey("stepNumber") && i.Data.ContainsKey("logFileLineNumber") && i.Type == IssueType.Notice).Count() == 1)), Times.AtLeastOnce);
}
}
@@ -626,12 +626,12 @@ namespace GitHub.Runner.Common.Tests.Worker
ec.StepTelemetry.StepId = Guid.NewGuid();
ec.StepTelemetry.Stage = "main";
ec.AddIssue(ec.CreateIssue(IssueType.Error, "error", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Warning, "warning", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Notice, "notice", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Error, "error", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Warning, "warning", null, true));
ec.AddIssue(ec.CreateIssue(IssueType.Notice, "notice", null, true));
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" });
ec.Complete();
@@ -692,9 +692,9 @@ namespace GitHub.Runner.Common.Tests.Worker
embeddedStep.StepTelemetry.Action = "actions/checkout";
embeddedStep.StepTelemetry.Ref = "v2";
embeddedStep.AddIssue(ec.CreateIssue(IssueType.Error, "error", null, true));
embeddedStep.AddIssue(ec.CreateIssue(IssueType.Warning, "warning", null, true));
embeddedStep.AddIssue(ec.CreateIssue(IssueType.Notice, "notice", null, true));
embeddedStep.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" });
embeddedStep.PublishStepTelemetry();
@@ -870,7 +870,7 @@ namespace GitHub.Runner.Common.Tests.Worker
inputVarsContext["VARIABLE_2"] = new StringContextData("value2");
jobRequest.ContextData["vars"] = inputVarsContext;
// Arrange: Setup the paging logger.
// Arrange: Setup the paging logger.
var pagingLogger1 = new Mock<IPagingLogger>();
var jobServerQueue = new Mock<IJobServerQueue>();
hc.EnqueueInstance(pagingLogger1.Object);
@@ -884,7 +884,7 @@ namespace GitHub.Runner.Common.Tests.Worker
var expected = new DictionaryContextData();
expected["VARIABLE_1"] = new StringContextData("value1");
expected["VARIABLE_2"] = new StringContextData("value1");
Assert.True(ExpressionValuesAssertEqual(expected, jobContext.ExpressionValues["vars"] as DictionaryContextData));
}
}
@@ -915,7 +915,7 @@ namespace GitHub.Runner.Common.Tests.Worker
inputVarsContext[Constants.Variables.Actions.RunnerDebug] = new StringContextData("true");
jobRequest.ContextData["vars"] = inputVarsContext;
// Arrange: Setup the paging logger.
// Arrange: Setup the paging logger.
var pagingLogger1 = new Mock<IPagingLogger>();
var jobServerQueue = new Mock<IJobServerQueue>();
hc.EnqueueInstance(pagingLogger1.Object);
@@ -926,7 +926,7 @@ namespace GitHub.Runner.Common.Tests.Worker
jobContext.InitializeJob(jobRequest, CancellationToken.None);
Assert.Equal("true", jobContext.Global.Variables.Get(Constants.Variables.Actions.StepDebug));
Assert.Equal("true", jobContext.Global.Variables.Get(Constants.Variables.Actions.RunnerDebug));
}
@@ -961,7 +961,7 @@ namespace GitHub.Runner.Common.Tests.Worker
jobRequest.Variables[Constants.Variables.Actions.StepDebug] = "false";
jobRequest.Variables[Constants.Variables.Actions.RunnerDebug] = "false";
// Arrange: Setup the paging logger.
// Arrange: Setup the paging logger.
var pagingLogger1 = new Mock<IPagingLogger>();
var jobServerQueue = new Mock<IJobServerQueue>();
hc.EnqueueInstance(pagingLogger1.Object);
@@ -972,7 +972,7 @@ namespace GitHub.Runner.Common.Tests.Worker
jobContext.InitializeJob(jobRequest, CancellationToken.None);
Assert.Equal("false", jobContext.Global.Variables.Get(Constants.Variables.Actions.StepDebug));
Assert.Equal("false", jobContext.Global.Variables.Get(Constants.Variables.Actions.RunnerDebug));
}

View File

@@ -9,7 +9,6 @@ using GitHub.Runner.Sdk;
using GitHub.Runner.Worker;
using GitHub.Runner.Worker.Container;
using GitHub.Runner.Worker.Handlers;
using GitHub.Services.Common;
using Moq;
using Xunit;
using DTWebApi = GitHub.DistributedTask.WebApi;
@@ -22,7 +21,7 @@ namespace GitHub.Runner.Common.Tests.Worker
private Mock<IActionCommandManager> _commandManager;
private Variables _variables;
private OnMatcherChanged _onMatcherChanged;
private List<DTWebApi.IReadOnlyIssue> _issues;
private List<Tuple<DTWebApi.Issue, string>> _issues;
private List<string> _messages;
private List<string> _commands;
private OutputManager _outputManager;
@@ -83,10 +82,10 @@ namespace GitHub.Runner.Common.Tests.Worker
Process("ERROR: message 4");
Process("NOT GOOD: message 5");
Assert.Equal(4, _issues.Count);
Assert.Equal("message 1", _issues[0].Message);
Assert.Equal("message 2", _issues[1].Message);
Assert.Equal("message 3", _issues[2].Message);
Assert.Equal("message 5", _issues[3].Message);
Assert.Equal("message 1", _issues[0].Item1.Message);
Assert.Equal("message 2", _issues[1].Item1.Message);
Assert.Equal("message 3", _issues[2].Item1.Message);
Assert.Equal("message 5", _issues[3].Item1.Message);
Assert.Equal(0, _commands.Count);
Assert.Equal(1, _messages.Count);
Assert.Equal("ERROR: message 4", _messages[0]);
@@ -149,11 +148,11 @@ namespace GitHub.Runner.Common.Tests.Worker
Process("ERROR: message 4");
Process("NOT GOOD: message 5");
Assert.Equal(5, _issues.Count);
Assert.Equal("message 1", _issues[0].Message);
Assert.Equal("message 2", _issues[1].Message);
Assert.Equal("message 3", _issues[2].Message);
Assert.Equal("message 4", _issues[3].Message);
Assert.Equal("message 5", _issues[4].Message);
Assert.Equal("message 1", _issues[0].Item1.Message);
Assert.Equal("message 2", _issues[1].Item1.Message);
Assert.Equal("message 3", _issues[2].Item1.Message);
Assert.Equal("message 4", _issues[3].Item1.Message);
Assert.Equal("message 5", _issues[4].Item1.Message);
Assert.Equal(0, _commands.Count);
Assert.Equal(0, _messages.Count);
}
@@ -189,10 +188,10 @@ namespace GitHub.Runner.Common.Tests.Worker
Process("BAD: real bad");
Process(": not working");
Assert.Equal(2, _issues.Count);
Assert.Equal("real bad", _issues[0].Message);
Assert.Equal("BAD", _issues[0]["code"]);
Assert.Equal("not working", _issues[1].Message);
Assert.Null(_issues[1]["code"]);
Assert.Equal("real bad", _issues[0].Item1.Message);
Assert.Equal("BAD", _issues[0].Item1.Data["code"]);
Assert.Equal("not working", _issues[1].Item1.Message);
Assert.False(_issues[1].Item1.Data.ContainsKey("code"));
Assert.Equal(0, _commands.Count);
Assert.Equal(0, _messages.Count);
}
@@ -239,11 +238,11 @@ namespace GitHub.Runner.Common.Tests.Worker
Process("Error: real bad");
Process("regular message 2");
Assert.Equal(5, _issues.Count);
Assert.Equal("it broke", _issues[0].Message);
Assert.Equal("oh no", _issues[1].Message);
Assert.Equal("not good", _issues[2].Message);
Assert.Equal("it broke again", _issues[3].Message);
Assert.Equal("real bad", _issues[4].Message);
Assert.Equal("it broke", _issues[0].Item1.Message);
Assert.Equal("oh no", _issues[1].Item1.Message);
Assert.Equal("not good", _issues[2].Item1.Message);
Assert.Equal("it broke again", _issues[3].Item1.Message);
Assert.Equal("real bad", _issues[4].Item1.Message);
Assert.Equal(0, _commands.Count);
Assert.Equal(4, _messages.Count);
Assert.Equal("Start: hello", _messages[0]);
@@ -294,8 +293,8 @@ namespace GitHub.Runner.Common.Tests.Worker
Process("ERROR: it is broken");
Process("NOT GOOD: that did not work");
Assert.Equal(2, _issues.Count);
Assert.Equal("it is broken", _issues[0].Message);
Assert.Equal("that did not work", _issues[1].Message);
Assert.Equal("it is broken", _issues[0].Item1.Message);
Assert.Equal("that did not work", _issues[1].Item1.Message);
Assert.Equal(0, _commands.Count);
Assert.Equal(0, _messages.Count);
}
@@ -333,15 +332,15 @@ namespace GitHub.Runner.Common.Tests.Worker
Process("(12,thirty-four): it is broken");
Process("(twelve,34): not working");
Assert.Equal(3, _issues.Count);
Assert.Equal("real bad", _issues[0].Message);
Assert.Equal("12", _issues[0]["line"]);
Assert.Equal("34", _issues[0]["col"]);
Assert.Equal("it is broken", _issues[1].Message);
Assert.Equal("12", _issues[1]["line"]);
Assert.Null(_issues[1]["col"]);
Assert.Equal("not working", _issues[2].Message);
Assert.Null(_issues[2]["line"]);
Assert.Equal("34", _issues[2]["col"]);
Assert.Equal("real bad", _issues[0].Item1.Message);
Assert.Equal("12", _issues[0].Item1.Data["line"]);
Assert.Equal("34", _issues[0].Item1.Data["col"]);
Assert.Equal("it is broken", _issues[1].Item1.Message);
Assert.Equal("12", _issues[1].Item1.Data["line"]);
Assert.False(_issues[1].Item1.Data.ContainsKey("col"));
Assert.Equal("not working", _issues[2].Item1.Message);
Assert.False(_issues[2].Item1.Data.ContainsKey("line"));
Assert.Equal("34", _issues[2].Item1.Data["col"]);
Assert.Equal(0, _commands.Count);
Assert.Equal(2, _messages.Count);
Assert.Equal("##[debug]Unable to parse column number 'thirty-four'", _messages[0]);
@@ -374,8 +373,8 @@ namespace GitHub.Runner.Common.Tests.Worker
Process("this line is a command too ##[some-command]even though it contains ERROR: not working again");
Process("##[not-command]this line is an ERROR: it is broken again");
Assert.Equal(2, _issues.Count);
Assert.Equal("it is broken", _issues[0].Message);
Assert.Equal("it is broken again", _issues[1].Message);
Assert.Equal("it is broken", _issues[0].Item1.Message);
Assert.Equal("it is broken again", _issues[1].Item1.Message);
Assert.Equal(2, _commands.Count);
Assert.Equal("##[some-command]this line is a command even though it contains ERROR: not working", _commands[0]);
Assert.Equal("this line is a command too ##[some-command]even though it contains ERROR: not working again", _commands[1]);
@@ -405,7 +404,8 @@ namespace GitHub.Runner.Common.Tests.Worker
});
Process("the error: \033[31mred, \033[1;31mbright red, \033[mreset");
Assert.Equal(1, _issues.Count);
Assert.Equal("red, bright red, reset", _issues[0].Message);
Assert.Equal("red, bright red, reset", _issues[0].Item1.Message);
Assert.Equal("the error: red, bright red, reset", _issues[0].Item2);
Assert.Equal(0, _commands.Count);
Assert.Equal(0, _messages.Count);
}
@@ -455,9 +455,9 @@ namespace GitHub.Runner.Common.Tests.Worker
Process("ERROR: message 3");
Process("NOT GOOD: message 4");
Assert.Equal(3, _issues.Count);
Assert.Equal("message 1", _issues[0].Message);
Assert.Equal("message 2", _issues[1].Message);
Assert.Equal("message 4", _issues[2].Message);
Assert.Equal("message 1", _issues[0].Item1.Message);
Assert.Equal("message 2", _issues[1].Item1.Message);
Assert.Equal("message 4", _issues[2].Item1.Message);
Assert.Equal(0, _commands.Count);
Assert.Equal(1, _messages.Count);
Assert.Equal("ERROR: message 3", _messages[0]);
@@ -517,8 +517,8 @@ namespace GitHub.Runner.Common.Tests.Worker
Process("Matches both line 1: hello again");
Process("oh no, another error");
Assert.Equal(2, _issues.Count);
Assert.Equal("it broke", _issues[0].Message);
Assert.Equal("oh no, another error", _issues[1].Message);
Assert.Equal("it broke", _issues[0].Item1.Message);
Assert.Equal("oh no, another error", _issues[1].Item1.Message);
Assert.Equal(0, _commands.Count);
Assert.Equal(4, _messages.Count);
Assert.Equal("Matches both line 1: hello", _messages[0]);
@@ -573,14 +573,14 @@ namespace GitHub.Runner.Common.Tests.Worker
Process(": not working");
Process("ERROR! uh oh");
Assert.Equal(4, _issues.Count);
Assert.Equal("real bad", _issues[0].Message);
Assert.Equal(DTWebApi.IssueType.Error, _issues[0].Type);
Assert.Equal("not great", _issues[1].Message);
Assert.Equal(DTWebApi.IssueType.Warning, _issues[1].Type);
Assert.Equal("not working", _issues[2].Message);
Assert.Equal(DTWebApi.IssueType.Error, _issues[2].Type);
Assert.Equal("uh oh", _issues[3].Message);
Assert.Equal(DTWebApi.IssueType.Error, _issues[3].Type);
Assert.Equal("real bad", _issues[0].Item1.Message);
Assert.Equal(DTWebApi.IssueType.Error, _issues[0].Item1.Type);
Assert.Equal("not great", _issues[1].Item1.Message);
Assert.Equal(DTWebApi.IssueType.Warning, _issues[1].Item1.Type);
Assert.Equal("not working", _issues[2].Item1.Message);
Assert.Equal(DTWebApi.IssueType.Error, _issues[2].Item1.Type);
Assert.Equal("uh oh", _issues[3].Item1.Message);
Assert.Equal(DTWebApi.IssueType.Error, _issues[3].Item1.Type);
Assert.Equal(0, _commands.Count);
Assert.Equal(2, _messages.Count);
Assert.StartsWith("##[debug]Skipped", _messages[0]);
@@ -633,9 +633,9 @@ namespace GitHub.Runner.Common.Tests.Worker
Process("jane.doe@contoso.com");
Process("ERR: this error");
Assert.Equal(3, _issues.Count);
Assert.Equal("john.doe@contoso.com", _issues[0].Message);
Assert.Contains("Removing issue matcher 'email'", _issues[1].Message);
Assert.Equal("this error", _issues[2].Message);
Assert.Equal("john.doe@contoso.com", _issues[0].Item1.Message);
Assert.Contains("Removing issue matcher 'email'", _issues[1].Item1.Message);
Assert.Equal("this error", _issues[2].Item1.Message);
Assert.Equal(0, _commands.Count);
Assert.Equal(2, _messages.Where(x => x.StartsWith("##[debug]Timeout processing issue matcher")).Count());
Assert.Equal(1, _messages.Where(x => x.Equals("t@t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.c%20")).Count());
@@ -725,32 +725,32 @@ namespace GitHub.Runner.Common.Tests.Worker
Assert.Equal(9, _issues.Count);
Assert.Equal("some error 1", _issues[0].Message);
Assert.Null(_issues[0]["file"]);
Assert.Equal("some error 1", _issues[0].Item1.Message);
Assert.False(_issues[0].Item1.Data.ContainsKey("file"));
Assert.Equal("some error 2", _issues[1].Message);
Assert.Equal(file_workflowRepository.Substring(workflowRepository.Length + 1).Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), _issues[1]["file"]);
Assert.Equal("some error 2", _issues[1].Item1.Message);
Assert.Equal(file_workflowRepository.Substring(workflowRepository.Length + 1).Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), _issues[1].Item1.Data["file"]);
Assert.Equal("some error 3", _issues[2].Message);
Assert.Equal(file_workflowRepository.Substring(workflowRepository.Length + 1).Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), _issues[2]["file"]);
Assert.Equal("some error 3", _issues[2].Item1.Message);
Assert.Equal(file_workflowRepository.Substring(workflowRepository.Length + 1).Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), _issues[2].Item1.Data["file"]);
Assert.Equal("some error 4", _issues[3].Message);
Assert.Equal(file_workflowRepository_nestedDirectory.Substring(workflowRepository.Length + 1).Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), _issues[3]["file"]);
Assert.Equal("some error 4", _issues[3].Item1.Message);
Assert.Equal(file_workflowRepository_nestedDirectory.Substring(workflowRepository.Length + 1).Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), _issues[3].Item1.Data["file"]);
Assert.Equal("some error 5", _issues[4].Message);
Assert.Null(_issues[4]["file"]);
Assert.Equal("some error 5", _issues[4].Item1.Message);
Assert.False(_issues[4].Item1.Data.ContainsKey("file"));
Assert.Equal("some error 6", _issues[5].Message);
Assert.Null(_issues[5]["file"]);
Assert.Equal("some error 6", _issues[5].Item1.Message);
Assert.False(_issues[5].Item1.Data.ContainsKey("file"));
Assert.Equal("some error 7", _issues[6].Message);
Assert.Null(_issues[6]["file"]);
Assert.Equal("some error 7", _issues[6].Item1.Message);
Assert.False(_issues[6].Item1.Data.ContainsKey("file"));
Assert.Equal("some error 8", _issues[7].Message);
Assert.Equal(file_nestedWorkflowRepository.Substring(nestedWorkflowRepository.Length + 1).Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), _issues[7]["file"]);
Assert.Equal("some error 8", _issues[7].Item1.Message);
Assert.Equal(file_nestedWorkflowRepository.Substring(nestedWorkflowRepository.Length + 1).Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), _issues[7].Item1.Data["file"]);
Assert.Equal("some error 9", _issues[8].Message);
Assert.Equal(file_workflowRepositoryUsingSsh.Substring(workflowRepositoryUsingSsh.Length + 1).Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), _issues[8]["file"]);
Assert.Equal("some error 9", _issues[8].Item1.Message);
Assert.Equal(file_workflowRepositoryUsingSsh.Substring(workflowRepositoryUsingSsh.Length + 1).Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), _issues[8].Item1.Data["file"]);
}
Environment.SetEnvironmentVariable("RUNNER_TEST_GET_REPOSITORY_PATH_FAILSAFE", "");
@@ -810,11 +810,11 @@ namespace GitHub.Runner.Common.Tests.Worker
Assert.Equal(2, _issues.Count);
Assert.Equal("some error 1", _issues[0].Message);
Assert.Equal("some-file.txt", _issues[0]["file"]);
Assert.Equal("some error 1", _issues[0].Item1.Message);
Assert.Equal("some-file.txt", _issues[0].Item1.Data["file"]);
Assert.Equal("some error 2", _issues[1].Message);
Assert.Equal("some-file.txt", _issues[1]["file"]);
Assert.Equal("some error 2", _issues[1].Item1.Message);
Assert.Equal("some-file.txt", _issues[1].Item1.Data["file"]);
}
}
@@ -871,11 +871,11 @@ namespace GitHub.Runner.Common.Tests.Worker
Assert.Equal(2, _issues.Count);
Assert.Equal("some error 1", _issues[0].Message);
Assert.Equal("some-file.txt", _issues[0]["file"]);
Assert.Equal("some error 1", _issues[0].Item1.Message);
Assert.Equal("some-file.txt", _issues[0].Item1.Data["file"]);
Assert.Equal("some error 2", _issues[1].Message);
Assert.Equal("some-file.txt", _issues[1]["file"]);
Assert.Equal("some error 2", _issues[1].Item1.Message);
Assert.Equal("some-file.txt", _issues[1].Item1.Data["file"]);
}
}
#endif
@@ -929,8 +929,8 @@ namespace GitHub.Runner.Common.Tests.Worker
// Process
Process("some-directory/some-file.txt: some error [workflow-repo/some-project/some-project.proj]");
Assert.Equal(1, _issues.Count);
Assert.Equal("some error", _issues[0].Message);
Assert.Equal("some-project/some-directory/some-file.txt", _issues[0]["file"]);
Assert.Equal("some error", _issues[0].Item1.Message);
Assert.Equal("some-project/some-directory/some-file.txt", _issues[0].Item1.Data["file"]);
Assert.Equal(0, _commands.Count);
Assert.Equal(0, _messages.Count);
}
@@ -958,7 +958,7 @@ namespace GitHub.Runner.Common.Tests.Worker
matchers?.Validate();
_onMatcherChanged = null;
_issues = new List<DTWebApi.IReadOnlyIssue>();
_issues = new List<Tuple<DTWebApi.Issue, string>>();
_messages = new List<string>();
_commands = new List<string>();
@@ -983,12 +983,10 @@ namespace GitHub.Runner.Common.Tests.Worker
{
_onMatcherChanged = handler;
});
_executionContext.Setup(x => x.CreateIssue(It.IsAny<DTWebApi.IssueType>(), It.IsAny<string>(), It.IsAny<IssueMetadata>(), It.IsAny<bool>()))
.Returns(TestUtil.CreateTestIssue);
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.IReadOnlyIssue>()))
.Callback((DTWebApi.IReadOnlyIssue issue) =>
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<string>()))
.Callback((DTWebApi.Issue issue, string logMessage) =>
{
_issues.Add(issue);
_issues.Add(new Tuple<DTWebApi.Issue, string>(issue, logMessage));
});
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
.Callback((string tag, string message) =>

View File

@@ -21,7 +21,7 @@ namespace GitHub.Runner.Common.Tests.Worker
public sealed class SaveStateFileCommandL0
{
private Mock<IExecutionContext> _executionContext;
private List<DTWebApi.IReadOnlyIssue> _issues;
private List<Tuple<DTWebApi.Issue, string>> _issues;
private string _rootDirectory;
private SaveStateFileCommand _saveStateFileCommand;
private Dictionary<string, string> _intraActionState;
@@ -390,7 +390,7 @@ namespace GitHub.Runner.Common.Tests.Worker
private TestHostContext Setup([CallerMemberName] string name = "")
{
_issues = new List<DTWebApi.IReadOnlyIssue>();
_issues = new List<Tuple<DTWebApi.Issue, string>>();
_intraActionState = new Dictionary<string, string>();
var hostContext = new TestHostContext(this, name);
@@ -413,11 +413,12 @@ namespace GitHub.Runner.Common.Tests.Worker
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
WriteDebug = true,
});
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.IReadOnlyIssue>()))
.Callback((DTWebApi.IReadOnlyIssue issue) =>
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<string>()))
.Callback((DTWebApi.Issue issue, string logMessage) =>
{
_issues.Add(issue);
_trace.Info($"Issue '{issue.Type}': {issue.Message}");
_issues.Add(new Tuple<DTWebApi.Issue, string>(issue, logMessage));
var message = !string.IsNullOrEmpty(logMessage) ? logMessage : issue.Message;
_trace.Info($"Issue '{issue.Type}': {message}");
});
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
.Callback((string tag, string message) =>

View File

@@ -1,11 +1,17 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using GitHub.Runner.Worker;
using GitHub.Runner.Worker.Container;
using GitHub.Runner.Worker.Handlers;
using Moq;
using Xunit;
using DTWebApi = GitHub.DistributedTask.WebApi;
@@ -15,7 +21,7 @@ namespace GitHub.Runner.Common.Tests.Worker
public sealed class SetEnvFileCommandL0
{
private Mock<IExecutionContext> _executionContext;
private List<DTWebApi.IReadOnlyIssue> _issues;
private List<Tuple<DTWebApi.Issue, string>> _issues;
private string _rootDirectory;
private SetEnvFileCommand _setEnvFileCommand;
private ITraceWriter _trace;
@@ -383,7 +389,7 @@ namespace GitHub.Runner.Common.Tests.Worker
private TestHostContext Setup([CallerMemberName] string name = "")
{
_issues = new List<DTWebApi.IReadOnlyIssue>();
_issues = new List<Tuple<DTWebApi.Issue, string>>();
var hostContext = new TestHostContext(this, name);
@@ -405,11 +411,12 @@ namespace GitHub.Runner.Common.Tests.Worker
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
WriteDebug = true,
});
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.IReadOnlyIssue>()))
.Callback((DTWebApi.IReadOnlyIssue issue) =>
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<string>()))
.Callback((DTWebApi.Issue issue, string logMessage) =>
{
_issues.Add(issue);
_trace.Info($"Issue '{issue.Type}': {issue.Message}");
_issues.Add(new Tuple<DTWebApi.Issue, string>(issue, logMessage));
var message = !string.IsNullOrEmpty(logMessage) ? logMessage : issue.Message;
_trace.Info($"Issue '{issue.Type}': {message}");
});
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
.Callback((string tag, string message) =>

View File

@@ -1,11 +1,17 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using GitHub.Runner.Worker;
using GitHub.Runner.Worker.Container;
using GitHub.Runner.Worker.Handlers;
using Moq;
using Xunit;
using DTWebApi = GitHub.DistributedTask.WebApi;
@@ -15,7 +21,7 @@ namespace GitHub.Runner.Common.Tests.Worker
public sealed class SetOutputFileCommandL0
{
private Mock<IExecutionContext> _executionContext;
private List<DTWebApi.IReadOnlyIssue> _issues;
private List<Tuple<DTWebApi.Issue, string>> _issues;
private Dictionary<string, string> _outputs;
private string _rootDirectory;
private SetOutputFileCommand _setOutputFileCommand;
@@ -384,7 +390,7 @@ namespace GitHub.Runner.Common.Tests.Worker
private TestHostContext Setup([CallerMemberName] string name = "")
{
_issues = new List<DTWebApi.IReadOnlyIssue>();
_issues = new List<Tuple<DTWebApi.Issue, string>>();
_outputs = new Dictionary<string, string>();
var hostContext = new TestHostContext(this, name);
@@ -407,11 +413,12 @@ namespace GitHub.Runner.Common.Tests.Worker
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
WriteDebug = true,
});
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.IReadOnlyIssue>()))
.Callback((DTWebApi.IReadOnlyIssue issue) =>
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<string>()))
.Callback((DTWebApi.Issue issue, string logMessage) =>
{
_issues.Add(issue);
_trace.Info($"Issue '{issue.Type}': {issue.Message}");
_issues.Add(new Tuple<DTWebApi.Issue, string>(issue, logMessage));
var message = !string.IsNullOrEmpty(logMessage) ? logMessage : issue.Message;
_trace.Info($"Issue '{issue.Type}': {message}");
});
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
.Callback((string tag, string message) =>

View File

@@ -1 +1 @@
2.299.1
2.301.1