Compare commits

..

6 Commits

Author SHA1 Message Date
ruvceskistefan
3f6b4be9af Issue 1596: Runner throws null ref exception when new line after EOF is missing (#1687)
* Issue 1596: runner throws nullref exception when writting env var

* Adding tests for missing new line after EOF marker

* Changing newline to new line
2022-02-24 12:57:42 +00:00
Nikola Jokic
b3938017b5 Repaired hashFiles call so if error was thrown, it was returned to process invoker (#1678)
* hashFiles.ts added exit status on promise action

* generated layoutbin/hashfiles/index.js
2022-02-24 12:57:42 +00:00
ruvceskistefan
acb46a3e1e Issue 1261: inconsistency of outputs (both canceled and cancelled are used) (#1624)
* Issue 1261: inconsistency of outputs

* Changing cancelled to canceled in one error message
2022-02-24 12:57:42 +00:00
Tingluo Huang
5e90b3ebb5 Add SHA to useragent. (#1694) 2022-02-24 12:57:42 +00:00
Thomas Boop
493a0bd8fc Revert "revert node12 version due to fs.copyFileSync hang https://git… (#1651)
* Revert "revert node12 version due to fs.copyFileSync hang https://github.com/actions/runner/issues/1536 (#1537)"

bef164a12f

* check hashs before tests because tests rely on right values + update hashes

* fix tests

* use hc trace
2022-02-24 12:57:42 +00:00
Nikola Jokic
e5d19459a7 RunnerService.js added logic to fail on N attempts 2022-02-17 14:08:01 +01:00
54 changed files with 4318 additions and 3773 deletions

View File

@@ -260,17 +260,6 @@ jobs:
console.log(releaseNote) console.log(releaseNote)
core.setOutput('version', runnerVersion); core.setOutput('version', runnerVersion);
core.setOutput('note', releaseNote); core.setOutput('note', releaseNote);
- name: Validate Packages HASH
working-directory: _package
run: |
ls -l
echo "${{needs.build.outputs.win-x64-sha}} actions-runner-win-x64-${{ steps.releaseNote.outputs.version }}.zip" | shasum -a 256 -c
echo "${{needs.build.outputs.osx-x64-sha}} actions-runner-osx-x64-${{ steps.releaseNote.outputs.version }}.tar.gz" | shasum -a 256 -c
echo "${{needs.build.outputs.linux-x64-sha}} actions-runner-linux-x64-${{ steps.releaseNote.outputs.version }}.tar.gz" | shasum -a 256 -c
echo "${{needs.build.outputs.linux-arm-sha}} actions-runner-linux-arm-${{ steps.releaseNote.outputs.version }}.tar.gz" | shasum -a 256 -c
echo "${{needs.build.outputs.linux-arm64-sha}} actions-runner-linux-arm64-${{ steps.releaseNote.outputs.version }}.tar.gz" | shasum -a 256 -c
# Create GitHub release # Create GitHub release
- uses: actions/create-release@master - uses: actions/create-release@master
id: createRelease id: createRelease

View File

@@ -1,19 +1,20 @@
## Features ## Features
- Continue-on-error is now possible for the composite action steps (#1763)
- Now it's possible to use context evaluation in the `shell` of composite action run steps (#1767) - Add Runner Configuration option to disable auto update `--disableupdate` (#1558)
- Introduce `GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY` env variable to skip SSL Cert Verification on the Runner (#1616)
- Adds support for downloading trimmed versions of the runner when the entire package does not need to be upgraded (#1568)
## Bugs ## Bugs
- Fix a bug where job would be marked as 'cancelled' after self-hosted runner going offline (#1792) - Set Outcome/Conclusion for composite action steps (#1600)
- Translate paths in `github` and `runner` contexts when running on a container (#1762)
- Warn about invalid flags when configuring or running the runner (#1781)
- Fix a bug where job hooks would use job level working directory (#1809)
## Misc ## Misc
- Allow warnings about actions using Node v12 (#1735)
- Better exception handling when runner is configured with invalid Url or token (#1741) - Update `run.sh` to more gracefully handle updates (#1494)
- Set user agent for websocket requests (#1791) - Use 8Mb default chunking for File Container Uploads (#1626)
- Gracefully handle websocket failures (#1789) - Performance improvements in handling large amounts of live logs (#1592)
- Capture telemetry when git errors on unsafe repository. (#1823) - Allow `./svc.sh stop` to exit as soon as runner process exits (#1580)
- Add additional tracing to help troubleshoot job message corruption (#1587)
## Windows x64 ## 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. 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 @@
2.290.1 <Update to ./src/runnerversion when creating release>

View File

@@ -1,6 +1,6 @@
{ {
"plugins": ["@typescript-eslint"], "plugins": ["jest", "@typescript-eslint"],
"extends": ["plugin:github/recommended"], "extends": ["plugin:github/es6"],
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"parserOptions": { "parserOptions": {
"ecmaVersion": 9, "ecmaVersion": 9,
@@ -17,16 +17,13 @@
"@typescript-eslint/no-require-imports": "error", "@typescript-eslint/no-require-imports": "error",
"@typescript-eslint/array-type": "error", "@typescript-eslint/array-type": "error",
"@typescript-eslint/await-thenable": "error", "@typescript-eslint/await-thenable": "error",
"@typescript-eslint/naming-convention": [ "@typescript-eslint/ban-ts-ignore": "error",
"error",
{
"selector": "default",
"format": ["camelCase"]
}
],
"camelcase": "off", "camelcase": "off",
"@typescript-eslint/camelcase": "error",
"@typescript-eslint/class-name-casing": "error",
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}], "@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
"@typescript-eslint/func-call-spacing": ["error", "never"], "@typescript-eslint/func-call-spacing": ["error", "never"],
"@typescript-eslint/generic-type-naming": ["error", "^[A-Z][A-Za-z]*$"],
"@typescript-eslint/no-array-constructor": "error", "@typescript-eslint/no-array-constructor": "error",
"@typescript-eslint/no-empty-interface": "error", "@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-explicit-any": "error",
@@ -36,6 +33,7 @@
"@typescript-eslint/no-misused-new": "error", "@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-namespace": "error", "@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-non-null-assertion": "warn", "@typescript-eslint/no-non-null-assertion": "warn",
"@typescript-eslint/no-object-literal-type-assertion": "error",
"@typescript-eslint/no-unnecessary-qualifier": "error", "@typescript-eslint/no-unnecessary-qualifier": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error", "@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-useless-constructor": "error", "@typescript-eslint/no-useless-constructor": "error",
@@ -43,19 +41,19 @@
"@typescript-eslint/prefer-for-of": "warn", "@typescript-eslint/prefer-for-of": "warn",
"@typescript-eslint/prefer-function-type": "warn", "@typescript-eslint/prefer-function-type": "warn",
"@typescript-eslint/prefer-includes": "error", "@typescript-eslint/prefer-includes": "error",
"@typescript-eslint/prefer-interface": "error",
"@typescript-eslint/prefer-string-starts-ends-with": "error", "@typescript-eslint/prefer-string-starts-ends-with": "error",
"@typescript-eslint/promise-function-async": "error", "@typescript-eslint/promise-function-async": "error",
"@typescript-eslint/require-array-sort-compare": "error", "@typescript-eslint/require-array-sort-compare": "error",
"@typescript-eslint/restrict-plus-operands": "error", "@typescript-eslint/restrict-plus-operands": "error",
"semi": "off",
"@typescript-eslint/semi": ["error", "never"], "@typescript-eslint/semi": ["error", "never"],
"@typescript-eslint/type-annotation-spacing": "error", "@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/unbound-method": "error", "@typescript-eslint/unbound-method": "error"
"filenames/match-regex" : "off",
"github/no-then" : 1, // warning
"semi": "off"
}, },
"env": { "env": {
"node": true, "node": true,
"es6": true "es6": true,
"jest/globals": true
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -25,10 +25,10 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^12.7.12", "@types/node": "^12.7.12",
"@typescript-eslint/parser": "^5.15.0", "@typescript-eslint/parser": "^2.8.0",
"@zeit/ncc": "^0.20.5", "@zeit/ncc": "^0.20.5",
"eslint": "^8.11.0", "eslint": "^6.8.0",
"eslint-plugin-github": "^4.3.5", "eslint-plugin-github": "^2.0.0",
"prettier": "^1.19.1", "prettier": "^1.19.1",
"typescript": "^3.6.4" "typescript": "^3.6.4"
} }

View File

@@ -1,9 +1,9 @@
import * as glob from '@actions/glob'
import * as crypto from 'crypto' import * as crypto from 'crypto'
import * as fs from 'fs' import * as fs from 'fs'
import * as glob from '@actions/glob'
import * as path from 'path'
import * as stream from 'stream' import * as stream from 'stream'
import * as util from 'util' import * as util from 'util'
import * as path from 'path'
async function run(): Promise<void> { async function run(): Promise<void> {
// arg0 -> node // arg0 -> node

View File

@@ -4,6 +4,7 @@
var childProcess = require("child_process"); var childProcess = require("child_process");
var path = require("path"); var path = require("path");
const { exit } = require("process");
var supported = ["linux", "darwin"]; var supported = ["linux", "darwin"];
@@ -26,7 +27,7 @@ if (exitServiceAfterNFailures <= 0) {
var consecutiveFailureCount = 0; var consecutiveFailureCount = 0;
var gracefulShutdown = function () { var gracefulShutdown = function (code) {
console.log("Shutting down runner listener"); console.log("Shutting down runner listener");
stopping = true; stopping = true;
if (listener) { if (listener) {
@@ -108,7 +109,7 @@ var runService = function () {
console.error( console.error(
`${messagePrefix}, exiting service after ${consecutiveFailureCount} consecutive failures` `${messagePrefix}, exiting service after ${consecutiveFailureCount} consecutive failures`
); );
gracefulShutdown(); gracefulShutdown(5);
return; return;
} else { } else {
console.log(`${messagePrefix}, re-launch runner in 5 seconds.`); console.log(`${messagePrefix}, re-launch runner in 5 seconds.`);
@@ -129,9 +130,9 @@ runService();
console.log("Started running service"); console.log("Started running service");
process.on("SIGINT", () => { process.on("SIGINT", () => {
gracefulShutdown(); gracefulShutdown(0);
}); });
process.on("SIGTERM", () => { process.on("SIGTERM", () => {
gracefulShutdown(); gracefulShutdown(0);
}); });

View File

@@ -63,7 +63,7 @@ function install()
mkdir -p "${log_path}" || failed "failed to create ${log_path}" mkdir -p "${log_path}" || failed "failed to create ${log_path}"
echo Creating ${PLIST_PATH} echo Creating ${PLIST_PATH}
sed "s/{{User}}/${USER:-$SUDO_USER}/g; s/{{SvcName}}/$SVC_NAME/g; s@{{RunnerRoot}}@${RUNNER_ROOT}@g; s@{{UserHome}}@$HOME@g;" "${TEMPLATE_PATH}" > "${TEMP_PATH}" || failed "failed to create replacement temp file" sed "s/{{User}}/${SUDO_USER:-$USER}/g; s/{{SvcName}}/$SVC_NAME/g; s@{{RunnerRoot}}@${RUNNER_ROOT}@g; s@{{UserHome}}@$HOME@g;" "${TEMPLATE_PATH}" > "${TEMP_PATH}" || failed "failed to create replacement temp file"
mv "${TEMP_PATH}" "${PLIST_PATH}" || failed "failed to copy plist" mv "${TEMP_PATH}" "${PLIST_PATH}" || failed "failed to copy plist"
# Since we started with sudo, runsvc.sh will be owned by root. Change this to current login user. # Since we started with sudo, runsvc.sh will be owned by root. Change this to current login user.

View File

@@ -1557,12 +1557,12 @@ var __importStar = (this && this.__importStar) || function (mod) {
return result; return result;
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const glob = __importStar(__webpack_require__(281));
const crypto = __importStar(__webpack_require__(417)); const crypto = __importStar(__webpack_require__(417));
const fs = __importStar(__webpack_require__(747)); const fs = __importStar(__webpack_require__(747));
const glob = __importStar(__webpack_require__(281));
const path = __importStar(__webpack_require__(622));
const stream = __importStar(__webpack_require__(413)); const stream = __importStar(__webpack_require__(413));
const util = __importStar(__webpack_require__(669)); const util = __importStar(__webpack_require__(669));
const path = __importStar(__webpack_require__(622));
function run() { function run() {
var e_1, _a; var e_1, _a;
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {

View File

@@ -30,13 +30,13 @@ date "+[%F %T-%4N] Waiting for $runnerprocessname ($runnerpid) to complete" >> "
while [ -e /proc/$runnerpid ] while [ -e /proc/$runnerpid ]
do do
date "+[%F %T-%4N] Process $runnerpid still running" >> "$logfile" 2>&1 date "+[%F %T-%4N] Process $runnerpid still running" >> "$logfile" 2>&1
"$rootfolder"/safe_sleep.sh 2 sleep 2
done done
date "+[%F %T-%4N] Process $runnerpid finished running" >> "$logfile" 2>&1 date "+[%F %T-%4N] Process $runnerpid finished running" >> "$logfile" 2>&1
# start re-organize folders # start re-organize folders
date "+[%F %T-%4N] Sleep 1 more second to make sure process exited" >> "$logfile" 2>&1 date "+[%F %T-%4N] Sleep 1 more second to make sure process exited" >> "$logfile" 2>&1
"$rootfolder"/safe_sleep.sh 1 sleep 1
# the folder structure under runner root will be # the folder structure under runner root will be
# ./bin -> bin.2.100.0 (junction folder) # ./bin -> bin.2.100.0 (junction folder)

View File

@@ -10,6 +10,22 @@ fi
# Run # Run
shopt -s nocasematch shopt -s nocasematch
safe_sleep() {
if [ ! -x "$(command -v sleep)" ]; then
if [ ! -x "$(command -v ping)" ]; then
COUNT="0"
while [[ $COUNT != 5000 ]]; do
echo "SLEEP" > /dev/null
COUNT=$[$COUNT+1]
done
else
ping -c 5 127.0.0.1 > /dev/null
fi
else
sleep 5
fi
}
SOURCE="${BASH_SOURCE[0]}" SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
@@ -28,18 +44,18 @@ elif [[ $returnCode == 1 ]]; then
exit 0 exit 0
elif [[ $returnCode == 2 ]]; then elif [[ $returnCode == 2 ]]; then
echo "Runner listener exit with retryable error, re-launch runner in 5 seconds." echo "Runner listener exit with retryable error, re-launch runner in 5 seconds."
"$DIR"/safe_sleep.sh 5 safe_sleep
exit 2 exit 1
elif [[ $returnCode == 3 ]]; then elif [[ $returnCode == 3 ]]; then
# Sleep 5 seconds to wait for the runner update process finish # Sleep 5 seconds to wait for the runner update process finish
echo "Runner listener exit because of updating, re-launch runner in 5 seconds" echo "Runner listener exit because of updating, re-launch runner in 5 seconds"
"$DIR"/safe_sleep.sh 5 safe_sleep
exit 2 exit 1
elif [[ $returnCode == 4 ]]; then elif [[ $returnCode == 4 ]]; then
# Sleep 5 seconds to wait for the ephemeral runner update process finish # Sleep 5 seconds to wait for the ephemeral runner update process finish
echo "Runner listener exit because of updating, re-launch ephemeral runner in 5 seconds" echo "Runner listener exit because of updating, re-launch ephemeral runner in 5 seconds"
"$DIR"/safe_sleep.sh 5 safe_sleep
exit 2 exit 1
else else
echo "Exiting with unknown error code: ${returnCode}" echo "Exiting with unknown error code: ${returnCode}"
exit 0 exit 0

View File

@@ -15,7 +15,7 @@ while :;
do do
"$DIR"/run-helper.sh $* "$DIR"/run-helper.sh $*
returnCode=$? returnCode=$?
if [[ $returnCode -eq 2 ]]; then if [[ $returnCode == 1 ]]; then
echo "Restarting runner..." echo "Restarting runner..."
else else
echo "Exiting runner..." echo "Exiting runner..."

View File

@@ -1,6 +0,0 @@
#!/bin/bash
SECONDS=0
while [[ $SECONDS != $1 ]]; do
:
done

View File

@@ -86,7 +86,7 @@ namespace GitHub.Runner.Common
public static class CommandLine public static class CommandLine
{ {
//if you are adding a new arg, please make sure you update the //if you are adding a new arg, please make sure you update the
//validOptions dictionary as well present in the CommandSettings.cs //validArgs array as well present in the CommandSettings.cs
public static class Args public static class Args
{ {
public static readonly string Auth = "auth"; public static readonly string Auth = "auth";
@@ -121,7 +121,7 @@ namespace GitHub.Runner.Common
} }
//if you are adding a new flag, please make sure you update the //if you are adding a new flag, please make sure you update the
//validOptions dictionary as well present in the CommandSettings.cs //validFlags array as well present in the CommandSettings.cs
public static class Flags public static class Flags
{ {
public static readonly string Check = "check"; public static readonly string Check = "check";
@@ -149,8 +149,6 @@ namespace GitHub.Runner.Common
public static class Features public static class Features
{ {
public static readonly string DiskSpaceWarning = "runner.diskspace.warning"; public static readonly string DiskSpaceWarning = "runner.diskspace.warning";
public static readonly string Node12Warning = "DistributedTask.AddWarningToNode12Action";
public static readonly string UseContainerPathForTemplate = "DistributedTask.UseContainerPathForTemplate";
} }
public static readonly string InternalTelemetryIssueDataKey = "_internal_telemetry"; public static readonly string InternalTelemetryIssueDataKey = "_internal_telemetry";
@@ -158,9 +156,7 @@ namespace GitHub.Runner.Common
public static readonly string LowDiskSpace = "LOW_DISK_SPACE"; public static readonly string LowDiskSpace = "LOW_DISK_SPACE";
public static readonly string UnsupportedCommand = "UNSUPPORTED_COMMAND"; public static readonly string UnsupportedCommand = "UNSUPPORTED_COMMAND";
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 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 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/en/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 Node12DetectedAfterEndOfLife = "Node.js 12 actions are deprecated. Please update the following actions to use Node.js 16: {0}";
} }
public static class RunnerEvent public static class RunnerEvent
@@ -192,12 +188,6 @@ namespace GitHub.Runner.Common
public static readonly string Success = "success"; public static readonly string Success = "success";
} }
public static class Hooks
{
public static readonly string JobStartedStepName = "Set up runner";
public static readonly string JobCompletedStepName = "Complete runner";
}
public static class Path public static class Path
{ {
public static readonly string ActionsDirectory = "_actions"; public static readonly string ActionsDirectory = "_actions";
@@ -229,15 +219,14 @@ namespace GitHub.Runner.Common
public static readonly string AllowUnsupportedStopCommandTokens = "ACTIONS_ALLOW_UNSECURE_STOPCOMMAND_TOKENS"; public static readonly string AllowUnsupportedStopCommandTokens = "ACTIONS_ALLOW_UNSECURE_STOPCOMMAND_TOKENS";
public static readonly string RunnerDebug = "ACTIONS_RUNNER_DEBUG"; public static readonly string RunnerDebug = "ACTIONS_RUNNER_DEBUG";
public static readonly string StepDebug = "ACTIONS_STEP_DEBUG"; public static readonly string StepDebug = "ACTIONS_STEP_DEBUG";
public static readonly string AllowActionsUseUnsecureNodeVersion = "ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION";
} }
public static class Agent public static class Agent
{ {
public static readonly string ToolsDirectory = "agent.ToolsDirectory"; public static readonly string ToolsDirectory = "agent.ToolsDirectory";
// Set this env var to "node12" to downgrade the node version for internal functions (e.g hashfiles). This does NOT affect the version of node actions. // Set this env var to force a node version for internal functions (e.g hashfiles). This does NOT affect the version of node actions.
public static readonly string ForcedInternalNodeVersion = "ACTIONS_RUNNER_FORCED_INTERNAL_NODE_VERSION"; public static readonly string ForcedNodeVersion = "GITHUB_ACTIONS_RUNNER_FORCED_NODE_VERSION";
} }
public static class System public static class System

View File

@@ -2,31 +2,24 @@ using GitHub.DistributedTask.WebApi;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.WebSockets;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using GitHub.Services.Common; using GitHub.Services.Common;
using GitHub.Services.WebApi; using GitHub.Services.WebApi;
using GitHub.Services.WebApi.Utilities.Internal;
using Newtonsoft.Json;
namespace GitHub.Runner.Common namespace GitHub.Runner.Common
{ {
[ServiceLocator(Default = typeof(JobServer))] [ServiceLocator(Default = typeof(JobServer))]
public interface IJobServer : IRunnerService, IAsyncDisposable public interface IJobServer : IRunnerService
{ {
Task ConnectAsync(VssConnection jobConnection); Task ConnectAsync(VssConnection jobConnection);
void InitializeWebsocketClient(ServiceEndpoint serviceEndpoint);
// logging and console // logging and console
Task<TaskLog> AppendLogContentAsync(Guid scopeIdentifier, string hubName, Guid planId, int logId, Stream uploadStream, CancellationToken cancellationToken); 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 AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, 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<TaskAttachment> CreateAttachmentAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, String type, String name, Stream uploadStream, CancellationToken cancellationToken);
Task<TaskLog> CreateLogAsync(Guid scopeIdentifier, string hubName, Guid planId, TaskLog log, 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<Timeline> CreateTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken);
@@ -41,20 +34,6 @@ namespace GitHub.Runner.Common
private bool _hasConnection; private bool _hasConnection;
private VssConnection _connection; private VssConnection _connection;
private TaskHttpClient _taskClient; private TaskHttpClient _taskClient;
private ClientWebSocket _websocketClient;
private ServiceEndpoint _serviceEndpoint;
private int totalBatchedLinesAttemptedByWebsocket = 0;
private int failedAttemptsToPostBatchedLinesByWebsocket = 0;
private static readonly TimeSpan _minDelayForWebsocketReconnect = TimeSpan.FromMilliseconds(100);
private static readonly TimeSpan _maxDelayForWebsocketReconnect = TimeSpan.FromMilliseconds(500);
private static readonly int _minWebsocketFailurePercentageAllowed = 50;
private static readonly int _minWebsocketBatchedLinesCountToConsider = 5;
private Task _websocketConnectTask;
public async Task ConnectAsync(VssConnection jobConnection) public async Task ConnectAsync(VssConnection jobConnection)
{ {
@@ -138,21 +117,6 @@ namespace GitHub.Runner.Common
} }
} }
public void InitializeWebsocketClient(ServiceEndpoint serviceEndpoint)
{
this._serviceEndpoint = serviceEndpoint;
InitializeWebsocketClient(TimeSpan.Zero);
}
public ValueTask DisposeAsync()
{
CloseWebSocket(WebSocketCloseStatus.NormalClosure, CancellationToken.None);
GC.SuppressFinalize(this);
return ValueTask.CompletedTask;
}
private void CheckConnection() private void CheckConnection()
{ {
if (!_hasConnection) if (!_hasConnection)
@@ -161,53 +125,6 @@ namespace GitHub.Runner.Common
} }
} }
private void InitializeWebsocketClient(TimeSpan delay)
{
if (_serviceEndpoint.Authorization != null &&
_serviceEndpoint.Authorization.Parameters.TryGetValue(EndpointAuthorizationParameters.AccessToken, out var accessToken) &&
!string.IsNullOrEmpty(accessToken))
{
if (_serviceEndpoint.Data.TryGetValue("FeedStreamUrl", out var feedStreamUrl) && !string.IsNullOrEmpty(feedStreamUrl))
{
// let's ensure we use the right scheme
feedStreamUrl = feedStreamUrl.Replace("https://", "wss://").Replace("http://", "ws://");
Trace.Info($"Creating websocket client ..." + feedStreamUrl);
this._websocketClient = new ClientWebSocket();
this._websocketClient.Options.SetRequestHeader("Authorization", $"Bearer {accessToken}");
var userAgentValues = new List<ProductInfoHeaderValue>();
userAgentValues.AddRange(UserAgentUtility.GetDefaultRestUserAgent());
userAgentValues.AddRange(HostContext.UserAgents);
this._websocketClient.Options.SetRequestHeader("User-Agent", string.Join(" ", userAgentValues.Select(x=>x.ToString())));
this._websocketConnectTask = ConnectWebSocketClient(feedStreamUrl, delay);
}
else
{
Trace.Info($"No FeedStreamUrl found, so we will use Rest API calls for sending feed data");
}
}
else
{
Trace.Info($"No access token from the service endpoint");
}
}
private async Task ConnectWebSocketClient(string feedStreamUrl, TimeSpan delay)
{
try
{
Trace.Info($"Attempting to start websocket client with delay {delay}.");
await Task.Delay(delay);
await this._websocketClient.ConnectAsync(new Uri(feedStreamUrl), default(CancellationToken));
Trace.Info($"Successfully started websocket client.");
}
catch(Exception ex)
{
Trace.Info("Exception caught during websocket client connect, fallback of HTTP would be used now instead of websocket.");
Trace.Error(ex);
}
}
//----------------------------------------------------------------- //-----------------------------------------------------------------
// Feedback: WebConsole, TimelineRecords and Logs // Feedback: WebConsole, TimelineRecords and Logs
//----------------------------------------------------------------- //-----------------------------------------------------------------
@@ -218,86 +135,16 @@ namespace GitHub.Runner.Common
return _taskClient.AppendLogContentAsync(scopeIdentifier, hubName, planId, logId, uploadStream, cancellationToken: cancellationToken); return _taskClient.AppendLogContentAsync(scopeIdentifier, hubName, planId, logId, uploadStream, cancellationToken: cancellationToken);
} }
public async Task AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, long? startLine, CancellationToken cancellationToken) public Task AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, CancellationToken cancellationToken)
{ {
CheckConnection(); CheckConnection();
var pushedLinesViaWebsocket = false; return _taskClient.AppendTimelineRecordFeedAsync(scopeIdentifier, hubName, planId, timelineId, timelineRecordId, stepId, lines, cancellationToken: cancellationToken);
if (_websocketConnectTask != null)
{
await _websocketConnectTask;
}
// "_websocketClient != null" implies either: We have a successful connection OR we have to attempt sending again and then reconnect
// ...in other words, if websocket client is null, we will skip sending to websocket and just use rest api calls to send data
if (_websocketClient != null)
{
var linesWrapper = startLine.HasValue? new TimelineRecordFeedLinesWrapper(stepId, lines, startLine.Value): new TimelineRecordFeedLinesWrapper(stepId, lines);
var jsonData = StringUtil.ConvertToJson(linesWrapper);
try
{
totalBatchedLinesAttemptedByWebsocket++;
var jsonDataBytes = Encoding.UTF8.GetBytes(jsonData);
// break the message into chunks of 1024 bytes
for (var i = 0; i < jsonDataBytes.Length; i += 1 * 1024)
{
var lastChunk = i + (1 * 1024) >= jsonDataBytes.Length;
var chunk = new ArraySegment<byte>(jsonDataBytes, i, Math.Min(1 * 1024, jsonDataBytes.Length - i));
await _websocketClient.SendAsync(chunk, WebSocketMessageType.Text, endOfMessage:lastChunk, cancellationToken);
}
pushedLinesViaWebsocket = true;
}
catch (Exception ex)
{
failedAttemptsToPostBatchedLinesByWebsocket++;
Trace.Info($"Caught exception during append web console line to websocket, let's fallback to sending via non-websocket call (total calls: {totalBatchedLinesAttemptedByWebsocket}, failed calls: {failedAttemptsToPostBatchedLinesByWebsocket}, websocket state: {this._websocketClient?.State}).");
Trace.Error(ex);
if (totalBatchedLinesAttemptedByWebsocket > _minWebsocketBatchedLinesCountToConsider)
{
// let's consider failure percentage
if (failedAttemptsToPostBatchedLinesByWebsocket * 100 / totalBatchedLinesAttemptedByWebsocket > _minWebsocketFailurePercentageAllowed)
{
Trace.Info($"Exhausted websocket allowed retries, we will not attempt websocket connection for this job to post lines again.");
CloseWebSocket(WebSocketCloseStatus.InternalServerError, cancellationToken);
// By setting it to null, we will ensure that we never try websocket path again for this job
_websocketClient = null;
}
}
if (_websocketClient != null)
{
var delay = BackoffTimerHelper.GetRandomBackoff(_minDelayForWebsocketReconnect, _maxDelayForWebsocketReconnect);
Trace.Info($"Websocket is not open, let's attempt to connect back again with random backoff {delay} ms (total calls: {totalBatchedLinesAttemptedByWebsocket}, failed calls: {failedAttemptsToPostBatchedLinesByWebsocket}).");
InitializeWebsocketClient(delay);
}
}
}
if (!pushedLinesViaWebsocket)
{
if (startLine.HasValue)
{
await _taskClient.AppendTimelineRecordFeedAsync(scopeIdentifier, hubName, planId, timelineId, timelineRecordId, stepId, lines, startLine.Value, cancellationToken: cancellationToken);
}
else
{
await _taskClient.AppendTimelineRecordFeedAsync(scopeIdentifier, hubName, planId, timelineId, timelineRecordId, stepId, lines, cancellationToken: cancellationToken);
}
}
} }
private void CloseWebSocket(WebSocketCloseStatus closeStatus, CancellationToken cancellationToken) public Task AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, long startLine, CancellationToken cancellationToken)
{ {
try CheckConnection();
{ return _taskClient.AppendTimelineRecordFeedAsync(scopeIdentifier, hubName, planId, timelineId, timelineRecordId, stepId, lines, startLine, cancellationToken: cancellationToken);
_websocketClient?.CloseOutputAsync(closeStatus, "Closing websocket", cancellationToken);
}
catch (Exception websocketEx)
{
// In some cases this might be okay since the websocket might be open yet, so just close and don't trace exceptions
Trace.Info($"Failed to close websocket gracefully {websocketEx.GetType().Name}");
}
} }
public Task<TaskAttachment> CreateAttachmentAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, string type, string name, Stream uploadStream, CancellationToken cancellationToken) public Task<TaskAttachment> CreateAttachmentAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, string type, string name, Stream uploadStream, CancellationToken cancellationToken)

View File

@@ -71,7 +71,7 @@ namespace GitHub.Runner.Common
// Web console dequeue will start with process queue every 250ms for the first 60*4 times (~60 seconds). // Web console dequeue will start with process queue every 250ms for the first 60*4 times (~60 seconds).
// Then the dequeue will happen every 500ms. // Then the dequeue will happen every 500ms.
// In this way, customer still can get instance live console output on job start, // In this way, customer still can get instance live console output on job start,
// at the same time we can cut the load to server after the build run for more than 60s // at the same time we can cut the load to server after the build run for more than 60s
private int _webConsoleLineAggressiveDequeueCount = 0; private int _webConsoleLineAggressiveDequeueCount = 0;
private const int _webConsoleLineAggressiveDequeueLimit = 4 * 60; private const int _webConsoleLineAggressiveDequeueLimit = 4 * 60;
@@ -89,10 +89,6 @@ namespace GitHub.Runner.Common
{ {
Trace.Entering(); Trace.Entering();
var serviceEndPoint = jobRequest.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
_jobServer.InitializeWebsocketClient(serviceEndPoint);
if (_queueInProcess) if (_queueInProcess)
{ {
Trace.Info("No-opt, all queue process tasks are running."); Trace.Info("No-opt, all queue process tasks are running.");
@@ -160,9 +156,6 @@ namespace GitHub.Runner.Common
await ProcessTimelinesUpdateQueueAsync(runOnce: true); await ProcessTimelinesUpdateQueueAsync(runOnce: true);
Trace.Info("Timeline update queue drained."); Trace.Info("Timeline update queue drained.");
Trace.Info($"Disposing job server ...");
await _jobServer.DisposeAsync();
Trace.Info("All queue process tasks have been stopped, and all queues are drained."); Trace.Info("All queue process tasks have been stopped, and all queues are drained.");
} }
@@ -299,7 +292,15 @@ namespace GitHub.Runner.Common
{ {
try try
{ {
await _jobServer.AppendTimelineRecordFeedAsync(_scopeIdentifier, _hubName, _planId, _jobTimelineId, _jobTimelineRecordId, stepRecordId, batch.Select(logLine => logLine.Line).ToList(), batch[0].LineNumber, default(CancellationToken)); // we will not requeue failed batch, since the web console lines are time sensitive.
if (batch[0].LineNumber.HasValue)
{
await _jobServer.AppendTimelineRecordFeedAsync(_scopeIdentifier, _hubName, _planId, _jobTimelineId, _jobTimelineRecordId, stepRecordId, batch.Select(logLine => logLine.Line).ToList(), batch[0].LineNumber.Value, default(CancellationToken));
}
else
{
await _jobServer.AppendTimelineRecordFeedAsync(_scopeIdentifier, _hubName, _planId, _jobTimelineId, _jobTimelineRecordId, stepRecordId, batch.Select(logLine => logLine.Line).ToList(), default(CancellationToken));
}
if (_firstConsoleOutputs) if (_firstConsoleOutputs)
{ {
@@ -488,8 +489,8 @@ namespace GitHub.Runner.Common
if (runOnce) if (runOnce)
{ {
// continue process timeline records update, // continue process timeline records update,
// we might have more records need update, // we might have more records need update,
// since we just create a new sub-timeline // since we just create a new sub-timeline
if (pendingSubtimelineUpdate) if (pendingSubtimelineUpdate)
{ {

View File

@@ -7,9 +7,9 @@ namespace GitHub.Runner.Common.Util
{ {
private const string _defaultNodeVersion = "node16"; private const string _defaultNodeVersion = "node16";
public static readonly ReadOnlyCollection<string> BuiltInNodeVersions = new(new[] {"node12", "node16"}); public static readonly ReadOnlyCollection<string> BuiltInNodeVersions = new(new[] {"node12", "node16"});
public static string GetInternalNodeVersion() public static string GetNodeVersion()
{ {
var forcedNodeVersion = Environment.GetEnvironmentVariable(Constants.Variables.Agent.ForcedInternalNodeVersion); var forcedNodeVersion = Environment.GetEnvironmentVariable(Constants.Variables.Agent.ForcedNodeVersion);
return !string.IsNullOrEmpty(forcedNodeVersion) && BuiltInNodeVersions.Contains(forcedNodeVersion) ? forcedNodeVersion : _defaultNodeVersion; return !string.IsNullOrEmpty(forcedNodeVersion) && BuiltInNodeVersions.Contains(forcedNodeVersion) ? forcedNodeVersion : _defaultNodeVersion;
} }
} }

View File

@@ -315,7 +315,7 @@ namespace GitHub.Runner.Listener.Check
}); });
var downloadCertScript = Path.Combine(hostContext.GetDirectory(WellKnownDirectory.Bin), "checkScripts", "downloadCert"); var downloadCertScript = Path.Combine(hostContext.GetDirectory(WellKnownDirectory.Bin), "checkScripts", "downloadCert");
var node = Path.Combine(hostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetInternalNodeVersion(), "bin", $"node{IOUtil.ExeExtension}"); var node = Path.Combine(hostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetNodeVersion(), "bin", $"node{IOUtil.ExeExtension}");
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Run '{node} \"{downloadCertScript}\"' "); result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Run '{node} \"{downloadCertScript}\"' ");
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} {StringUtil.ConvertToJson(env)}"); result.Logs.Add($"{DateTime.UtcNow.ToString("O")} {StringUtil.ConvertToJson(env)}");
await processInvoker.ExecuteAsync( await processInvoker.ExecuteAsync(

View File

@@ -145,7 +145,7 @@ namespace GitHub.Runner.Listener.Check
}); });
var makeWebRequestScript = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "checkScripts", "makeWebRequest.js"); var makeWebRequestScript = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "checkScripts", "makeWebRequest.js");
var node = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetInternalNodeVersion(), "bin", $"node{IOUtil.ExeExtension}"); var node = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetNodeVersion(), "bin", $"node{IOUtil.ExeExtension}");
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Run '{node} \"{makeWebRequestScript}\"' "); result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Run '{node} \"{makeWebRequestScript}\"' ");
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} {StringUtil.ConvertToJson(env)}"); result.Logs.Add($"{DateTime.UtcNow.ToString("O")} {StringUtil.ConvertToJson(env)}");
await processInvoker.ExecuteAsync( await processInvoker.ExecuteAsync(

View File

@@ -17,57 +17,43 @@ namespace GitHub.Runner.Listener
private readonly IPromptManager _promptManager; private readonly IPromptManager _promptManager;
private readonly Tracing _trace; private readonly Tracing _trace;
// Valid flags for all commands private readonly string[] validCommands =
private readonly string[] genericOptions =
{ {
Constants.Runner.CommandLine.Flags.Help, Constants.Runner.CommandLine.Commands.Configure,
Constants.Runner.CommandLine.Flags.Version, Constants.Runner.CommandLine.Commands.Remove,
Constants.Runner.CommandLine.Flags.Commit, Constants.Runner.CommandLine.Commands.Run,
Constants.Runner.CommandLine.Flags.Check Constants.Runner.CommandLine.Commands.Warmup,
}; };
// Valid flags and args for specific command - key: command, value: array of valid flags and args private readonly string[] validFlags =
private readonly Dictionary<string, string[]> validOptions = new Dictionary<string, string[]>
{ {
// Valid configure flags and args Constants.Runner.CommandLine.Flags.Check,
[Constants.Runner.CommandLine.Commands.Configure] = Constants.Runner.CommandLine.Flags.Commit,
new string[] Constants.Runner.CommandLine.Flags.DisableUpdate,
{ Constants.Runner.CommandLine.Flags.Ephemeral,
Constants.Runner.CommandLine.Flags.DisableUpdate, Constants.Runner.CommandLine.Flags.Help,
Constants.Runner.CommandLine.Flags.Ephemeral, Constants.Runner.CommandLine.Flags.Once,
Constants.Runner.CommandLine.Flags.Replace, Constants.Runner.CommandLine.Flags.Replace,
Constants.Runner.CommandLine.Flags.RunAsService, Constants.Runner.CommandLine.Flags.RunAsService,
Constants.Runner.CommandLine.Flags.Unattended, Constants.Runner.CommandLine.Flags.Unattended,
Constants.Runner.CommandLine.Args.Auth, Constants.Runner.CommandLine.Flags.Version
Constants.Runner.CommandLine.Args.Labels, };
Constants.Runner.CommandLine.Args.MonitorSocketAddress,
Constants.Runner.CommandLine.Args.Name, private readonly string[] validArgs =
Constants.Runner.CommandLine.Args.PAT, {
Constants.Runner.CommandLine.Args.RunnerGroup, Constants.Runner.CommandLine.Args.Auth,
Constants.Runner.CommandLine.Args.Token, Constants.Runner.CommandLine.Args.Labels,
Constants.Runner.CommandLine.Args.Url, Constants.Runner.CommandLine.Args.MonitorSocketAddress,
Constants.Runner.CommandLine.Args.UserName, Constants.Runner.CommandLine.Args.Name,
Constants.Runner.CommandLine.Args.WindowsLogonAccount, Constants.Runner.CommandLine.Args.PAT,
Constants.Runner.CommandLine.Args.WindowsLogonPassword, Constants.Runner.CommandLine.Args.RunnerGroup,
Constants.Runner.CommandLine.Args.Work Constants.Runner.CommandLine.Args.StartupType,
}, Constants.Runner.CommandLine.Args.Token,
// Valid remove flags and args Constants.Runner.CommandLine.Args.Url,
[Constants.Runner.CommandLine.Commands.Remove] = Constants.Runner.CommandLine.Args.UserName,
new string[] Constants.Runner.CommandLine.Args.WindowsLogonAccount,
{ Constants.Runner.CommandLine.Args.WindowsLogonPassword,
Constants.Runner.CommandLine.Args.Token, Constants.Runner.CommandLine.Args.Work
Constants.Runner.CommandLine.Args.PAT
},
// Valid run flags and args
[Constants.Runner.CommandLine.Commands.Run] =
new string[]
{
Constants.Runner.CommandLine.Flags.Once,
Constants.Runner.CommandLine.Args.StartupType
},
// valid warmup flags and args
[Constants.Runner.CommandLine.Commands.Warmup] =
new string[] { }
}; };
// Commands. // Commands.
@@ -140,48 +126,17 @@ namespace GitHub.Runner.Listener
List<string> unknowns = new List<string>(); List<string> unknowns = new List<string>();
// detect unknown commands // detect unknown commands
unknowns.AddRange(_parser.Commands.Where(x => !validOptions.Keys.Contains(x, StringComparer.OrdinalIgnoreCase))); unknowns.AddRange(_parser.Commands.Where(x => !validCommands.Contains(x, StringComparer.OrdinalIgnoreCase)));
if (unknowns.Count == 0) // detect unknown flags
{ unknowns.AddRange(_parser.Flags.Where(x => !validFlags.Contains(x, StringComparer.OrdinalIgnoreCase)));
// detect unknown flags and args for valid commands
foreach (var command in _parser.Commands) // detect unknown args
{ unknowns.AddRange(_parser.Args.Keys.Where(x => !validArgs.Contains(x, StringComparer.OrdinalIgnoreCase)));
if (validOptions.TryGetValue(command, out string[] options))
{
unknowns.AddRange(_parser.Flags.Where(x => !options.Contains(x, StringComparer.OrdinalIgnoreCase) && !genericOptions.Contains(x, StringComparer.OrdinalIgnoreCase)));
unknowns.AddRange(_parser.Args.Keys.Where(x => !options.Contains(x, StringComparer.OrdinalIgnoreCase)));
}
}
}
return unknowns; return unknowns;
} }
public string GetCommandName()
{
string command = string.Empty;
if (Configure)
{
command = Constants.Runner.CommandLine.Commands.Configure;
}
else if (Remove)
{
command = Constants.Runner.CommandLine.Commands.Remove;
}
else if (Run)
{
command = Constants.Runner.CommandLine.Commands.Run;
}
else if (Warmup)
{
command = Constants.Runner.CommandLine.Commands.Warmup;
}
return command;
}
// //
// Interactive flags. // Interactive flags.
// //

View File

@@ -624,12 +624,9 @@ namespace GitHub.Runner.Listener.Configuration
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("basic", base64EncodingToken); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("basic", base64EncodingToken);
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents); httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/vnd.github.v3+json"); httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/vnd.github.v3+json");
var responseStatus = System.Net.HttpStatusCode.OK;
try try
{ {
var response = await httpClient.PostAsync(githubApiUrl, new StringContent(string.Empty)); var response = await httpClient.PostAsync(githubApiUrl, new StringContent(string.Empty));
responseStatus = response.StatusCode;
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
@@ -637,6 +634,11 @@ namespace GitHub.Runner.Listener.Configuration
var jsonResponse = await response.Content.ReadAsStringAsync(); var jsonResponse = await response.Content.ReadAsStringAsync();
return StringUtil.ConvertFromJson<GitHubRunnerRegisterToken>(jsonResponse); return StringUtil.ConvertFromJson<GitHubRunnerRegisterToken>(jsonResponse);
} }
else if(response.StatusCode == System.Net.HttpStatusCode.NotFound)
{
// It doesn't make sense to retry in this case, so just stop
break;
}
else else
{ {
_term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'"); _term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'");
@@ -645,15 +647,15 @@ namespace GitHub.Runner.Listener.Configuration
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
} }
} }
catch(Exception ex) when (retryCount < 2 && responseStatus != System.Net.HttpStatusCode.NotFound) catch(Exception ex) when (retryCount < 2)
{ {
retryCount++; retryCount++;
Trace.Error($"Failed to get JIT runner token -- Atempt: {retryCount}"); Trace.Error($"Failed to get JIT runner token -- Atempt: {retryCount}");
Trace.Error(ex); Trace.Error(ex);
Trace.Info("Retrying in 5 seconds");
} }
} }
var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)); var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5));
Trace.Info($"Retrying in {backOff.Seconds} seconds");
await Task.Delay(backOff); await Task.Delay(backOff);
} }
return null; return null;
@@ -687,11 +689,9 @@ namespace GitHub.Runner.Listener.Configuration
{"runner_event", runnerEvent} {"runner_event", runnerEvent}
}; };
var responseStatus = System.Net.HttpStatusCode.OK;
try try
{ {
var response = await httpClient.PostAsync(githubApiUrl, new StringContent(StringUtil.ConvertToJson(bodyObject), null, "application/json")); var response = await httpClient.PostAsync(githubApiUrl, new StringContent(StringUtil.ConvertToJson(bodyObject), null, "application/json"));
responseStatus = response.StatusCode;
if(response.IsSuccessStatusCode) if(response.IsSuccessStatusCode)
{ {
@@ -699,23 +699,29 @@ namespace GitHub.Runner.Listener.Configuration
var jsonResponse = await response.Content.ReadAsStringAsync(); var jsonResponse = await response.Content.ReadAsStringAsync();
return StringUtil.ConvertFromJson<GitHubAuthResult>(jsonResponse); return StringUtil.ConvertFromJson<GitHubAuthResult>(jsonResponse);
} }
else if(response.StatusCode == System.Net.HttpStatusCode.NotFound)
{
// It doesn't make sense to retry in this case, so just stop
break;
}
else else
{ {
_term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'"); _term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'");
var errorResponse = await response.Content.ReadAsStringAsync(); var errorResponse = await response.Content.ReadAsStringAsync();
_term.WriteError(errorResponse); _term.WriteError(errorResponse);
// Something else bad happened, let's go to our retry logic
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
} }
} }
catch(Exception ex) when (retryCount < 2 && responseStatus != System.Net.HttpStatusCode.NotFound) catch(Exception ex) when (retryCount < 2)
{ {
retryCount++; retryCount++;
Trace.Error($"Failed to get tenant credentials -- Atempt: {retryCount}"); Trace.Error($"Failed to get tenant credentials -- Atempt: {retryCount}");
Trace.Error(ex); Trace.Error(ex);
Trace.Info("Retrying in 5 seconds");
} }
} }
var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)); var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5));
Trace.Info($"Retrying in {backOff.Seconds} seconds");
await Task.Delay(backOff); await Task.Delay(backOff);
} }
return null; return null;

View File

@@ -48,12 +48,13 @@ namespace GitHub.Runner.Listener.Configuration
string repoOrOrgName = regex.Replace(settings.RepoOrOrgName, "-"); string repoOrOrgName = regex.Replace(settings.RepoOrOrgName, "-");
serviceName = StringUtil.Format(serviceNamePattern, repoOrOrgName, settings.AgentName); serviceName = StringUtil.Format(serviceNamePattern, repoOrOrgName, settings.AgentName);
if (serviceName.Length > MaxServiceNameLength)
if (serviceName.Length > 80)
{ {
Trace.Verbose($"Calculated service name is too long (> {MaxServiceNameLength} chars). Trying again by calculating a shorter name."); Trace.Verbose($"Calculated service name is too long (> 80 chars). Trying again by calculating a shorter name.");
// Add 5 to add -xxxx random number on the end
int exceededCharLength = serviceName.Length - MaxServiceNameLength + 5; int exceededCharLength = serviceName.Length - 80;
string repoOrOrgNameSubstring = StringUtil.SubstringPrefix(repoOrOrgName, MaxRepoOrgCharacters); string repoOrOrgNameSubstring = StringUtil.SubstringPrefix(repoOrOrgName, 45);
exceededCharLength -= repoOrOrgName.Length - repoOrOrgNameSubstring.Length; exceededCharLength -= repoOrOrgName.Length - repoOrOrgNameSubstring.Length;
@@ -65,10 +66,6 @@ namespace GitHub.Runner.Listener.Configuration
runnerNameSubstring = StringUtil.SubstringPrefix(settings.AgentName, settings.AgentName.Length - exceededCharLength); runnerNameSubstring = StringUtil.SubstringPrefix(settings.AgentName, settings.AgentName.Length - exceededCharLength);
} }
// Lets add a suffix with a random number to reduce the chance of collisions between runner names once we truncate
var random = new Random();
var num = random.Next(1000, 9999).ToString();
runnerNameSubstring +=$"-{num}";
serviceName = StringUtil.Format(serviceNamePattern, repoOrOrgNameSubstring, runnerNameSubstring); serviceName = StringUtil.Format(serviceNamePattern, repoOrOrgNameSubstring, runnerNameSubstring);
} }
@@ -76,12 +73,5 @@ namespace GitHub.Runner.Listener.Configuration
Trace.Info($"Service name '{serviceName}' display name '{serviceDisplayName}' will be used for service configuration."); Trace.Info($"Service name '{serviceName}' display name '{serviceDisplayName}' will be used for service configuration.");
} }
#if (OS_LINUX || OS_OSX)
const int MaxServiceNameLength = 150;
const int MaxRepoOrgCharacters = 70;
#elif OS_WINDOWS
const int MaxServiceNameLength = 80;
const int MaxRepoOrgCharacters = 45;
#endif
} }
} }

View File

@@ -95,15 +95,7 @@ namespace GitHub.Runner.Listener
var unknownCommandlines = command.Validate(); var unknownCommandlines = command.Validate();
if (unknownCommandlines.Count > 0) if (unknownCommandlines.Count > 0)
{ {
string commandName = command.GetCommandName(); terminal.WriteError($"Unrecognized command-line input arguments: '{string.Join(", ", unknownCommandlines)}'. For usage refer to: .\\config.cmd --help or ./config.sh --help");
if (string.IsNullOrEmpty(commandName))
{
terminal.WriteError($"This command does not recognize the command-line input arguments: '{string.Join(", ", unknownCommandlines)}'. For usage refer to: .\\config.cmd --help or ./config.sh --help");
}
else
{
terminal.WriteError($"Unrecognized command-line input arguments for command {commandName}: '{string.Join(", ", unknownCommandlines)}'. For usage refer to: .\\config.cmd --help or ./config.sh --help");
}
} }
// Defer to the Runner class to execute the command. // Defer to the Runner class to execute the command.

View File

@@ -1070,7 +1070,7 @@ namespace GitHub.Runner.Listener
var stopWatch = Stopwatch.StartNew(); var stopWatch = Stopwatch.StartNew();
string binDir = HostContext.GetDirectory(WellKnownDirectory.Bin); string binDir = HostContext.GetDirectory(WellKnownDirectory.Bin);
string node = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetInternalNodeVersion(), "bin", $"node{IOUtil.ExeExtension}"); string node = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetNodeVersion(), "bin", $"node{IOUtil.ExeExtension}");
string hashFilesScript = Path.Combine(binDir, "hashFiles"); string hashFilesScript = Path.Combine(binDir, "hashFiles");
var hashResult = string.Empty; var hashResult = string.Empty;

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.IO;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using GitHub.DistributedTask.ObjectTemplating; using GitHub.DistributedTask.ObjectTemplating;
using GitHub.DistributedTask.ObjectTemplating.Tokens; using GitHub.DistributedTask.ObjectTemplating.Tokens;
@@ -140,7 +141,21 @@ namespace GitHub.Runner.Worker
IStepHost stepHost = HostContext.CreateService<IDefaultStepHost>(); IStepHost stepHost = HostContext.CreateService<IDefaultStepHost>();
ExecutionContext.WriteWebhookPayload(); // Makes directory for event_path data
var tempDirectory = HostContext.GetDirectory(WellKnownDirectory.Temp);
var workflowDirectory = Path.Combine(tempDirectory, "_github_workflow");
Directory.CreateDirectory(workflowDirectory);
var gitHubEvent = ExecutionContext.GetGitHubContext("event");
// adds the GitHub event path/file if the event exists
if (gitHubEvent != null)
{
var workflowFile = Path.Combine(workflowDirectory, "event.json");
Trace.Info($"Write event payload to {workflowFile}");
File.WriteAllText(workflowFile, gitHubEvent, new UTF8Encoding(false));
ExecutionContext.SetGitHubContext("event_path", workflowFile);
}
// Set GITHUB_ACTION_REPOSITORY if this Action is from a repository // Set GITHUB_ACTION_REPOSITORY if this Action is from a repository
if (Action.Reference is Pipelines.RepositoryPathReference repoPathReferenceAction && if (Action.Reference is Pipelines.RepositoryPathReference repoPathReferenceAction &&
@@ -171,16 +186,8 @@ namespace GitHub.Runner.Worker
// Load the inputs. // Load the inputs.
ExecutionContext.Debug("Loading inputs"); ExecutionContext.Debug("Loading inputs");
Dictionary<string, string> inputs; var templateEvaluator = ExecutionContext.ToPipelineTemplateEvaluator();
if (ExecutionContext.Global.Variables.GetBoolean(Constants.Runner.Features.UseContainerPathForTemplate) ?? false) var inputs = templateEvaluator.EvaluateStepInputs(Action.Inputs, ExecutionContext.ExpressionValues, ExecutionContext.ExpressionFunctions);
{
inputs = EvaluateStepInputs(stepHost);
}
else
{
var templateEvaluator = ExecutionContext.ToPipelineTemplateEvaluator();
inputs = templateEvaluator.EvaluateStepInputs(Action.Inputs, ExecutionContext.ExpressionValues, ExecutionContext.ExpressionFunctions);
}
var userInputs = new HashSet<string>(StringComparer.OrdinalIgnoreCase); var userInputs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (KeyValuePair<string, string> input in inputs) foreach (KeyValuePair<string, string> input in inputs)
@@ -307,15 +314,6 @@ namespace GitHub.Runner.Worker
return didFullyEvaluate; return didFullyEvaluate;
} }
private Dictionary<String, String> EvaluateStepInputs(IStepHost stepHost)
{
DictionaryContextData expressionValues = ExecutionContext.GetExpressionValues(stepHost);
var templateEvaluator = ExecutionContext.ToPipelineTemplateEvaluator();
var inputs = templateEvaluator.EvaluateStepInputs(Action.Inputs, expressionValues, ExecutionContext.ExpressionFunctions);
return inputs;
}
private string GenerateDisplayName(ActionStep action, DictionaryContextData contextData, IExecutionContext context, out bool didFullyEvaluate) private string GenerateDisplayName(ActionStep action, DictionaryContextData contextData, IExecutionContext context, out bool didFullyEvaluate)
{ {
ArgUtil.NotNull(context, nameof(context)); ArgUtil.NotNull(context, nameof(context));

View File

@@ -1,13 +1,17 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web;
using GitHub.DistributedTask.Expressions2; using GitHub.DistributedTask.Expressions2;
using GitHub.DistributedTask.ObjectTemplating.Tokens; using GitHub.DistributedTask.Pipelines;
using GitHub.DistributedTask.Pipelines.ContextData; using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.Pipelines.ObjectTemplating; using GitHub.DistributedTask.Pipelines.ObjectTemplating;
using GitHub.DistributedTask.WebApi; using GitHub.DistributedTask.WebApi;
@@ -15,7 +19,7 @@ using GitHub.Runner.Common;
using GitHub.Runner.Common.Util; using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using GitHub.Runner.Worker.Container; using GitHub.Runner.Worker.Container;
using GitHub.Runner.Worker.Handlers; using GitHub.Services.WebApi;
using Newtonsoft.Json; using Newtonsoft.Json;
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating; using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
using Pipelines = GitHub.DistributedTask.Pipelines; using Pipelines = GitHub.DistributedTask.Pipelines;
@@ -105,11 +109,6 @@ namespace GitHub.Runner.Worker
void ForceTaskComplete(); void ForceTaskComplete();
void RegisterPostJobStep(IStep step); void RegisterPostJobStep(IStep step);
void PublishStepTelemetry(); void PublishStepTelemetry();
void ApplyContinueOnError(TemplateToken continueOnError);
void UpdateGlobalStepsContext();
void WriteWebhookPayload();
} }
public sealed class ExecutionContext : RunnerService, IExecutionContext public sealed class ExecutionContext : RunnerService, IExecutionContext
@@ -439,19 +438,14 @@ namespace GitHub.Runner.Worker
_logger.End(); _logger.End();
UpdateGlobalStepsContext();
return Result.Value;
}
public void UpdateGlobalStepsContext()
{
// Skip if generated context name. Generated context names start with "__". After 3.2 the server will never send an empty context name. // Skip if generated context name. Generated context names start with "__". After 3.2 the server will never send an empty context name.
if (!string.IsNullOrEmpty(ContextName) && !ContextName.StartsWith("__", StringComparison.Ordinal)) if (!string.IsNullOrEmpty(ContextName) && !ContextName.StartsWith("__", StringComparison.Ordinal))
{ {
Global.StepsContext.SetOutcome(ScopeName, ContextName, (Outcome ?? Result ?? TaskResult.Succeeded).ToActionResult()); Global.StepsContext.SetOutcome(ScopeName, ContextName, (Outcome ?? Result ?? TaskResult.Succeeded).ToActionResult());
Global.StepsContext.SetConclusion(ScopeName, ContextName, (Result ?? TaskResult.Succeeded).ToActionResult()); Global.StepsContext.SetConclusion(ScopeName, ContextName, (Result ?? TaskResult.Succeeded).ToActionResult());
} }
return Result.Value;
} }
public void SetRunnerContext(string name, string value) public void SetRunnerContext(string name, string value)
@@ -556,15 +550,10 @@ namespace GitHub.Runner.Worker
issue.Message = issue.Message[.._maxIssueMessageLength]; 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 (issue.Type == IssueType.Error)
{ {
// tracking line number for each issue in log file
// log UI use this to navigate from issue to log
if (!string.IsNullOrEmpty(logMessage)) if (!string.IsNullOrEmpty(logMessage))
{ {
long logLineNumber = Write(WellKnownTags.Error, logMessage); long logLineNumber = Write(WellKnownTags.Error, logMessage);
@@ -580,6 +569,8 @@ namespace GitHub.Runner.Worker
} }
else if (issue.Type == IssueType.Warning) else if (issue.Type == IssueType.Warning)
{ {
// tracking line number for each issue in log file
// log UI use this to navigate from issue to log
if (!string.IsNullOrEmpty(logMessage)) if (!string.IsNullOrEmpty(logMessage))
{ {
long logLineNumber = Write(WellKnownTags.Warning, logMessage); long logLineNumber = Write(WellKnownTags.Warning, logMessage);
@@ -595,6 +586,9 @@ namespace GitHub.Runner.Worker
} }
else if (issue.Type == IssueType.Notice) else if (issue.Type == IssueType.Notice)
{ {
// tracking line number for each issue in log file
// log UI use this to navigate from issue to log
if (!string.IsNullOrEmpty(logMessage)) if (!string.IsNullOrEmpty(logMessage))
{ {
long logLineNumber = Write(WellKnownTags.Notice, logMessage); long logLineNumber = Write(WellKnownTags.Notice, logMessage);
@@ -686,7 +680,7 @@ namespace GitHub.Runner.Worker
if (Global.Variables.GetBoolean("DistributedTask.ForceInternalNodeVersionOnRunnerTo12") ?? false) if (Global.Variables.GetBoolean("DistributedTask.ForceInternalNodeVersionOnRunnerTo12") ?? false)
{ {
Environment.SetEnvironmentVariable(Constants.Variables.Agent.ForcedInternalNodeVersion, "node12"); Environment.SetEnvironmentVariable(Constants.Variables.Agent.ForcedNodeVersion, "node12");
} }
// Environment variables shared across all actions // Environment variables shared across all actions
@@ -997,24 +991,6 @@ namespace GitHub.Runner.Worker
} }
} }
public void WriteWebhookPayload()
{
// Makes directory for event_path data
var tempDirectory = HostContext.GetDirectory(WellKnownDirectory.Temp);
var workflowDirectory = Path.Combine(tempDirectory, "_github_workflow");
Directory.CreateDirectory(workflowDirectory);
var gitHubEvent = GetGitHubContext("event");
// adds the GitHub event path/file if the event exists
if (gitHubEvent != null)
{
var workflowFile = Path.Combine(workflowDirectory, "event.json");
Trace.Info($"Write event payload to {workflowFile}");
File.WriteAllText(workflowFile, gitHubEvent, new UTF8Encoding(false));
SetGitHubContext("event_path", workflowFile);
}
}
private void InitializeTimelineRecord(Guid timelineId, Guid timelineRecordId, Guid? parentTimelineRecordId, string recordType, string displayName, string refName, int? order) private void InitializeTimelineRecord(Guid timelineId, Guid timelineRecordId, Guid? parentTimelineRecordId, string recordType, string displayName, string refName, int? order)
{ {
_mainTimelineId = timelineId; _mainTimelineId = timelineId;
@@ -1069,36 +1045,6 @@ namespace GitHub.Runner.Worker
var newGuid = Guid.NewGuid(); var newGuid = Guid.NewGuid();
return CreateChild(newGuid, displayName, newGuid.ToString("N"), null, null, ActionRunStage.Post, intraActionState, _childTimelineRecordOrder - Root.PostJobSteps.Count, siblingScopeName: siblingScopeName); return CreateChild(newGuid, displayName, newGuid.ToString("N"), null, null, ActionRunStage.Post, intraActionState, _childTimelineRecordOrder - Root.PostJobSteps.Count, siblingScopeName: siblingScopeName);
} }
public void ApplyContinueOnError(TemplateToken continueOnErrorToken)
{
if (Result != TaskResult.Failed)
{
return;
}
var continueOnError = false;
try
{
var templateEvaluator = this.ToPipelineTemplateEvaluator();
continueOnError = templateEvaluator.EvaluateStepContinueOnError(continueOnErrorToken, ExpressionValues, ExpressionFunctions);
}
catch (Exception ex)
{
Trace.Info("The step failed and an error occurred when attempting to determine whether to continue on error.");
Trace.Error(ex);
this.Error("The step failed and an error occurred when attempting to determine whether to continue on error.");
this.Error(ex);
}
if (continueOnError)
{
Outcome = Result;
Result = TaskResult.Succeeded;
Trace.Info($"Updated step result (continue on error)");
}
UpdateGlobalStepsContext();
}
} }
// The Error/Warning/etc methods are created as extension methods to simplify unit testing. // The Error/Warning/etc methods are created as extension methods to simplify unit testing.
@@ -1120,6 +1066,7 @@ namespace GitHub.Runner.Worker
context.Error(ex.Message); context.Error(ex.Message);
context.Debug(ex.ToString()); context.Debug(ex.ToString());
} }
// Do not add a format string overload. See comment on ExecutionContext.Write(). // Do not add a format string overload. See comment on ExecutionContext.Write().
public static void Error(this IExecutionContext context, string message) public static void Error(this IExecutionContext context, string message)
{ {
@@ -1193,66 +1140,6 @@ namespace GitHub.Runner.Worker
{ {
return new TemplateTraceWriter(context); return new TemplateTraceWriter(context);
} }
public static DictionaryContextData GetExpressionValues(this IExecutionContext context, IStepHost stepHost)
{
if (stepHost is ContainerStepHost)
{
var expressionValues = context.ExpressionValues.Clone() as DictionaryContextData;
context.UpdatePathsInExpressionValues("github", expressionValues, stepHost);
context.UpdatePathsInExpressionValues("runner", expressionValues, stepHost);
return expressionValues;
}
else
{
return context.ExpressionValues.Clone() as DictionaryContextData;
}
}
private static void UpdatePathsInExpressionValues(this IExecutionContext context, string contextName, DictionaryContextData expressionValues, IStepHost stepHost)
{
var dict = expressionValues[contextName].AssertDictionary($"expected context {contextName} to be a dictionary");
context.ResolvePathsInExpressionValuesDictionary(dict, stepHost);
expressionValues[contextName] = dict;
}
private static void ResolvePathsInExpressionValuesDictionary(this IExecutionContext context, DictionaryContextData dict, IStepHost stepHost)
{
foreach (var key in dict.Keys.ToList())
{
if (dict[key] is StringContextData)
{
var value = dict[key].ToString();
if (!string.IsNullOrEmpty(value))
{
dict[key] = new StringContextData(stepHost.ResolvePathForStepHost(value));
}
}
else if (dict[key] is DictionaryContextData)
{
var innerDict = dict[key].AssertDictionary("expected dictionary");
context.ResolvePathsInExpressionValuesDictionary(innerDict, stepHost);
var updatedDict = new DictionaryContextData();
foreach (var k in innerDict.Keys.ToList())
{
updatedDict[k] = innerDict[k];
}
dict[key] = updatedDict;
}
else if (dict[key] is CaseSensitiveDictionaryContextData)
{
var innerDict = dict[key].AssertDictionary("expected dictionary");
context.ResolvePathsInExpressionValuesDictionary(innerDict, stepHost);
var updatedDict = new CaseSensitiveDictionaryContextData();
foreach (var k in innerDict.Keys.ToList())
{
updatedDict[k] = innerDict[k];
}
dict[key] = updatedDict;
}
}
}
} }
internal sealed class TemplateTraceWriter : ObjectTemplating.ITraceWriter internal sealed class TemplateTraceWriter : ObjectTemplating.ITraceWriter

View File

@@ -63,7 +63,7 @@ namespace GitHub.Runner.Worker.Expressions
string binDir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); string binDir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
string runnerRoot = new DirectoryInfo(binDir).Parent.FullName; string runnerRoot = new DirectoryInfo(binDir).Parent.FullName;
string node = Path.Combine(runnerRoot, "externals", NodeUtil.GetInternalNodeVersion(), "bin", $"node{IOUtil.ExeExtension}"); string node = Path.Combine(runnerRoot, "externals", NodeUtil.GetNodeVersion(), "bin", $"node{IOUtil.ExeExtension}");
string hashFilesScript = Path.Combine(binDir, "hashFiles"); string hashFilesScript = Path.Combine(binDir, "hashFiles");
var hashResult = string.Empty; var hashResult = string.Empty;
var p = new ProcessInvoker(new HashFilesTrace(context.Trace)); var p = new ProcessInvoker(new HashFilesTrace(context.Trace));

View File

@@ -298,7 +298,7 @@ namespace GitHub.Runner.Worker
if (fileSize > _attachmentSizeLimit) if (fileSize > _attachmentSizeLimit)
{ {
context.Error(String.Format(Constants.Runner.UnsupportedSummarySize, _attachmentSizeLimit / 1024, fileSize / 1024)); context.Error($"$GITHUB_STEP_SUMMARY supports content up a size of {_attachmentSizeLimit / 1024}k got {fileSize / 1024}k");
Trace.Info($"Step Summary file ({filePath}) is too large ({fileSize} bytes); skipping attachment upload"); Trace.Info($"Step Summary file ({filePath}) is too large ({fileSize} bytes); skipping attachment upload");
return; return;

View File

@@ -8,10 +8,10 @@ namespace GitHub.Runner.Worker
{ {
private readonly HashSet<string> _contextEnvAllowlist = new HashSet<string>(StringComparer.OrdinalIgnoreCase) private readonly HashSet<string> _contextEnvAllowlist = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{ {
"action",
"action_path", "action_path",
"action_ref", "action_ref",
"action_repository", "action_repository",
"action",
"actor", "actor",
"api_url", "api_url",
"base_ref", "base_ref",
@@ -22,12 +22,12 @@ namespace GitHub.Runner.Worker
"head_ref", "head_ref",
"job", "job",
"path", "path",
"ref",
"ref_name", "ref_name",
"ref_protected", "ref_protected",
"ref_type", "ref_type",
"ref",
"repository_owner",
"repository", "repository",
"repository_owner",
"retention_days", "retention_days",
"run_attempt", "run_attempt",
"run_id", "run_id",
@@ -35,7 +35,6 @@ namespace GitHub.Runner.Worker
"server_url", "server_url",
"sha", "sha",
"step_summary", "step_summary",
"triggering_actor",
"workflow", "workflow",
"workspace", "workspace",
}; };

View File

@@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using GitHub.DistributedTask.Expressions2; using GitHub.DistributedTask.Expressions2;
@@ -11,6 +13,7 @@ using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common; using GitHub.Runner.Common;
using GitHub.Runner.Common.Util; using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using GitHub.Runner.Worker;
using GitHub.Runner.Worker.Expressions; using GitHub.Runner.Worker.Expressions;
using Pipelines = GitHub.DistributedTask.Pipelines; using Pipelines = GitHub.DistributedTask.Pipelines;
@@ -83,7 +86,7 @@ namespace GitHub.Runner.Worker.Handlers
ExecutionContext.StepTelemetry.HasPreStep = Data.HasPre; ExecutionContext.StepTelemetry.HasPreStep = Data.HasPre;
ExecutionContext.StepTelemetry.HasPostStep = Data.HasPost; ExecutionContext.StepTelemetry.HasPostStep = Data.HasPost;
ExecutionContext.StepTelemetry.HasRunsStep = hasRunsStep; ExecutionContext.StepTelemetry.HasRunsStep = hasRunsStep;
ExecutionContext.StepTelemetry.HasUsesStep = hasUsesStep; ExecutionContext.StepTelemetry.HasUsesStep = hasUsesStep;
ExecutionContext.StepTelemetry.StepCount = steps.Count; ExecutionContext.StepTelemetry.StepCount = steps.Count;
@@ -404,7 +407,7 @@ namespace GitHub.Runner.Worker.Handlers
} }
// Update context // Update context
step.ExecutionContext.UpdateGlobalStepsContext(); SetStepsContext(step);
} }
} }
@@ -449,8 +452,6 @@ namespace GitHub.Runner.Worker.Handlers
SetStepConclusion(step, Common.Util.TaskResultUtil.MergeTaskResults(step.ExecutionContext.Result, step.ExecutionContext.CommandResult.Value)); SetStepConclusion(step, Common.Util.TaskResultUtil.MergeTaskResults(step.ExecutionContext.Result, step.ExecutionContext.CommandResult.Value));
} }
step.ExecutionContext.ApplyContinueOnError(step.ContinueOnError);
Trace.Info($"Step result: {step.ExecutionContext.Result}"); Trace.Info($"Step result: {step.ExecutionContext.Result}");
step.ExecutionContext.Debug($"Finished: {step.DisplayName}"); step.ExecutionContext.Debug($"Finished: {step.DisplayName}");
step.ExecutionContext.PublishStepTelemetry(); step.ExecutionContext.PublishStepTelemetry();
@@ -459,7 +460,16 @@ namespace GitHub.Runner.Worker.Handlers
private void SetStepConclusion(IStep step, TaskResult result) private void SetStepConclusion(IStep step, TaskResult result)
{ {
step.ExecutionContext.Result = result; step.ExecutionContext.Result = result;
step.ExecutionContext.UpdateGlobalStepsContext(); SetStepsContext(step);
}
private void SetStepsContext(IStep step)
{
if (!string.IsNullOrEmpty(step.ExecutionContext.ContextName) && !step.ExecutionContext.ContextName.StartsWith("__", StringComparison.Ordinal))
{
// TODO: when we support continue on error, we may need to do logic here to change conclusion based on the continue on error result
step.ExecutionContext.Global.StepsContext.SetOutcome(step.ExecutionContext.ScopeName, step.ExecutionContext.ContextName, (step.ExecutionContext.Result ?? TaskResult.Succeeded).ToActionResult());
step.ExecutionContext.Global.StepsContext.SetConclusion(step.ExecutionContext.ScopeName, step.ExecutionContext.ContextName, (step.ExecutionContext.Result ?? TaskResult.Succeeded).ToActionResult());
}
} }
} }
} }

View File

@@ -36,7 +36,6 @@ namespace GitHub.Runner.Worker.Handlers
protected IActionCommandManager ActionCommandManager { get; private set; } protected IActionCommandManager ActionCommandManager { get; private set; }
public Pipelines.ActionStepDefinitionReference Action { get; set; } public Pipelines.ActionStepDefinitionReference Action { get; set; }
public bool IsActionStep => Action != null;
public Dictionary<string, string> Environment { get; set; } public Dictionary<string, string> Environment { get; set; }
public Variables RuntimeVariables { get; set; } public Variables RuntimeVariables { get; set; }
public IExecutionContext ExecutionContext { get; set; } public IExecutionContext ExecutionContext { get; set; }
@@ -50,18 +49,13 @@ namespace GitHub.Runner.Worker.Handlers
// Print out action details // Print out action details
PrintActionDetails(stage); PrintActionDetails(stage);
// Get telemetry for the action // Get telemetry for the action.
PopulateActionTelemetry(stage); PopulateActionTelemetry();
} }
protected void PopulateActionTelemetry(ActionRunStage stage) protected void PopulateActionTelemetry()
{ {
if (!IsActionStep) if (Action.Type == Pipelines.ActionSourceType.ContainerRegistry)
{
ExecutionContext.StepTelemetry.Type = "runner";
ExecutionContext.StepTelemetry.Action = $"{stage} Job Hook";
}
else if (Action.Type == Pipelines.ActionSourceType.ContainerRegistry)
{ {
ExecutionContext.StepTelemetry.Type = "docker"; ExecutionContext.StepTelemetry.Type = "docker";
var registryAction = Action as Pipelines.ContainerRegistryReference; var registryAction = Action as Pipelines.ContainerRegistryReference;

View File

@@ -55,23 +55,7 @@ namespace GitHub.Runner.Worker.Handlers
else if (data.ExecutionType == ActionExecutionType.NodeJS) else if (data.ExecutionType == ActionExecutionType.NodeJS)
{ {
handler = HostContext.CreateService<INodeScriptActionHandler>(); handler = HostContext.CreateService<INodeScriptActionHandler>();
var nodeData = data as NodeJSActionExecutionData; (handler as INodeScriptActionHandler).Data = data as NodeJSActionExecutionData;
// With node12 EoL in 04/2022, we want to be able to uniformly upgrade all JS actions to node16 from the server
if (string.Equals(nodeData.NodeVersion, "node12", StringComparison.InvariantCultureIgnoreCase) &&
(executionContext.Global.Variables.GetBoolean("DistributedTask.ForceGithubJavascriptActionsToNode16") ?? false))
{
// The user can opt out of this behaviour by setting this variable to true, either setting 'env' in their workflow or as an environment variable on their machine
executionContext.Global.EnvironmentVariables.TryGetValue(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion, out var workflowOptOut);
var isWorkflowOptOutSet = !string.IsNullOrEmpty(workflowOptOut);
var isLocalOptOut = StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion));
bool isOptOut = isWorkflowOptOutSet ? StringUtil.ConvertToBoolean(workflowOptOut) : isLocalOptOut;
if (!isOptOut)
{
nodeData.NodeVersion = "node16";
}
}
(handler as INodeScriptActionHandler).Data = nodeData;
} }
else if (data.ExecutionType == ActionExecutionType.Script) else if (data.ExecutionType == ActionExecutionType.Script)
{ {

View File

@@ -3,11 +3,10 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using GitHub.DistributedTask.Pipelines;
using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.WebApi; using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common; using GitHub.Runner.Common;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using Pipelines = GitHub.DistributedTask.Pipelines;
namespace GitHub.Runner.Worker.Handlers namespace GitHub.Runner.Worker.Handlers
{ {
@@ -114,17 +113,6 @@ namespace GitHub.Runner.Worker.Handlers
// Remove environment variable that may cause conflicts with the node within the runner. // Remove environment variable that may cause conflicts with the node within the runner.
Environment.Remove("NODE_ICU_DATA"); // https://github.com/actions/runner/issues/795 Environment.Remove("NODE_ICU_DATA"); // https://github.com/actions/runner/issues/795
if (Data.NodeVersion == "node12" && (ExecutionContext.Global.Variables.GetBoolean(Constants.Runner.Features.Node12Warning) ?? false))
{
if (!ExecutionContext.JobContext.ContainsKey("Node12ActionsWarnings"))
{
ExecutionContext.JobContext["Node12ActionsWarnings"] = new ArrayContextData();
}
var repoAction = Action as RepositoryPathReference;
var actionDisplayName = new StringContextData(repoAction.Name ?? repoAction.Path); // local actions don't have a 'Name'
ExecutionContext.JobContext["Node12ActionsWarnings"].AssertArray("Node12ActionsWarnings").Add(actionDisplayName);
}
using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager)) using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager))
using (var stderrManager = new OutputManager(ExecutionContext, ActionCommandManager)) using (var stderrManager = new OutputManager(ExecutionContext, ActionCommandManager))
{ {

View File

@@ -151,11 +151,6 @@ namespace GitHub.Runner.Worker.Handlers
} }
} }
if (line.Contains("fatal: unsafe repository", StringComparison.OrdinalIgnoreCase))
{
_executionContext.StepTelemetry.ErrorMessages.Add(line);
}
// Regular output // Regular output
_executionContext.Output(line); _executionContext.Output(line);
} }

View File

@@ -24,22 +24,16 @@ namespace GitHub.Runner.Worker.Handlers
protected override void PrintActionDetails(ActionRunStage stage) protected override void PrintActionDetails(ActionRunStage stage)
{ {
// if we're executing a Job Extension, we won't have an 'Action'
if (!IsActionStep) if (stage == ActionRunStage.Post)
{ {
if (Inputs.TryGetValue("path", out var path)) throw new NotSupportedException("Script action should not have 'Post' job action.");
{
ExecutionContext.Output($"##[group]Run '{path}'");
}
else
{
throw new InvalidOperationException("Inputs 'path' must be set for job extensions");
}
} }
else if (Action.Type == Pipelines.ActionSourceType.Script)
Inputs.TryGetValue("script", out string contents);
contents = contents ?? string.Empty;
if (Action.Type == Pipelines.ActionSourceType.Script)
{ {
Inputs.TryGetValue("script", out string contents);
contents = contents ?? string.Empty;
var firstLine = contents.TrimStart(' ', '\t', '\r', '\n'); var firstLine = contents.TrimStart(' ', '\t', '\r', '\n');
var firstNewLine = firstLine.IndexOfAny(new[] { '\r', '\n' }); var firstNewLine = firstLine.IndexOfAny(new[] { '\r', '\n' });
if (firstNewLine >= 0) if (firstNewLine >= 0)
@@ -48,16 +42,17 @@ namespace GitHub.Runner.Worker.Handlers
} }
ExecutionContext.Output($"##[group]Run {firstLine}"); ExecutionContext.Output($"##[group]Run {firstLine}");
var multiLines = contents.Replace("\r\n", "\n").TrimEnd('\n').Split('\n');
foreach (var line in multiLines)
{
// Bright Cyan color
ExecutionContext.Output($"\x1b[36;1m{line}\x1b[0m");
}
} }
else else
{ {
throw new InvalidOperationException($"Invalid action type {Action?.Type} for {nameof(ScriptHandler)}"); throw new InvalidOperationException($"Invalid action type {Action.Type} for {nameof(ScriptHandler)}");
}
var multiLines = contents.Replace("\r\n", "\n").TrimEnd('\n').Split('\n');
foreach (var line in multiLines)
{
// Bright Cyan color
ExecutionContext.Output($"\x1b[36;1m{line}\x1b[0m");
} }
string argFormat; string argFormat;
@@ -137,6 +132,11 @@ namespace GitHub.Runner.Worker.Handlers
public async Task RunAsync(ActionRunStage stage) public async Task RunAsync(ActionRunStage stage)
{ {
if (stage == ActionRunStage.Post)
{
throw new NotSupportedException("Script action should not have 'Post' job action.");
}
// Validate args // Validate args
Trace.Entering(); Trace.Entering();
ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext)); ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext));
@@ -153,8 +153,7 @@ namespace GitHub.Runner.Worker.Handlers
string workingDirectory = null; string workingDirectory = null;
if (!Inputs.TryGetValue("workingDirectory", out workingDirectory)) if (!Inputs.TryGetValue("workingDirectory", out workingDirectory))
{ {
// Don't use job level working directories for hooks if (string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.Global.JobDefaults.TryGetValue("run", out var runDefaults))
if (IsActionStep && string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.Global.JobDefaults.TryGetValue("run", out var runDefaults))
{ {
if (runDefaults.TryGetValue("working-directory", out workingDirectory)) if (runDefaults.TryGetValue("working-directory", out workingDirectory))
{ {
@@ -213,8 +212,7 @@ namespace GitHub.Runner.Worker.Handlers
} }
} }
// Don't override runner telemetry here if (!string.IsNullOrEmpty(shellCommand))
if (!string.IsNullOrEmpty(shellCommand) && IsActionStep)
{ {
ExecutionContext.StepTelemetry.Action = shellCommand; ExecutionContext.StepTelemetry.Action = shellCommand;
} }
@@ -224,24 +222,10 @@ namespace GitHub.Runner.Worker.Handlers
{ {
throw new ArgumentException("Invalid shell option. Shell must be a valid built-in (bash, sh, cmd, powershell, pwsh) or a format string containing '{0}'"); throw new ArgumentException("Invalid shell option. Shell must be a valid built-in (bash, sh, cmd, powershell, pwsh) or a format string containing '{0}'");
} }
string scriptFilePath, resolvedScriptPath;
if (IsActionStep) // We do not not the full path until we know what shell is being used, so that we can determine the file extension
{ var scriptFilePath = Path.Combine(tempDirectory, $"{Guid.NewGuid()}{ScriptHandlerHelpers.GetScriptFileExtension(shellCommand)}");
// We do not not the full path until we know what shell is being used, so that we can determine the file extension var resolvedScriptPath = $"{StepHost.ResolvePathForStepHost(scriptFilePath).Replace("\"", "\\\"")}";
scriptFilePath = Path.Combine(tempDirectory, $"{Guid.NewGuid()}{ScriptHandlerHelpers.GetScriptFileExtension(shellCommand)}");
resolvedScriptPath = $"{StepHost.ResolvePathForStepHost(scriptFilePath).Replace("\"", "\\\"")}";
}
else
{
// JobExtensionRunners run a script file, we load that from the inputs here
if (!Inputs.ContainsKey("path"))
{
throw new ArgumentException("Expected 'path' input to be set");
}
scriptFilePath = Inputs["path"];
ArgUtil.NotNullOrEmpty(scriptFilePath, "path");
resolvedScriptPath = Inputs["path"].Replace("\"", "\\\"");
}
// Format arg string with script path // Format arg string with script path
var arguments = string.Format(argFormat, resolvedScriptPath); var arguments = string.Format(argFormat, resolvedScriptPath);
@@ -257,12 +241,9 @@ namespace GitHub.Runner.Worker.Handlers
#else #else
// Don't add a BOM. It causes the script to fail on some operating systems (e.g. on Ubuntu 14). // Don't add a BOM. It causes the script to fail on some operating systems (e.g. on Ubuntu 14).
var encoding = new UTF8Encoding(false); var encoding = new UTF8Encoding(false);
#endif #endif
if (IsActionStep) // Script is written to local path (ie host) but executed relative to the StepHost, which may be a container
{ File.WriteAllText(scriptFilePath, contents, encoding);
// Script is written to local path (ie host) but executed relative to the StepHost, which may be a container
File.WriteAllText(scriptFilePath, contents, encoding);
}
// Prepend PATH // Prepend PATH
AddPrependPathToEnvironment(); AddPrependPathToEnvironment();
@@ -285,7 +266,7 @@ namespace GitHub.Runner.Worker.Handlers
if (Environment.ContainsKey("DYLD_INSERT_LIBRARIES")) // We don't check `isContainerStepHost` because we don't support container on macOS if (Environment.ContainsKey("DYLD_INSERT_LIBRARIES")) // We don't check `isContainerStepHost` because we don't support container on macOS
{ {
// launch `node macOSRunInvoker.js shell args` instead of `shell args` to avoid macOS SIP remove `DYLD_INSERT_LIBRARIES` when launch process // launch `node macOSRunInvoker.js shell args` instead of `shell args` to avoid macOS SIP remove `DYLD_INSERT_LIBRARIES` when launch process
string node = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetInternalNodeVersion(), "bin", $"node{IOUtil.ExeExtension}"); string node = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetNodeVersion(), "bin", $"node{IOUtil.ExeExtension}");
string macOSRunInvoker = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "macos-run-invoker.js"); string macOSRunInvoker = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "macos-run-invoker.js");
arguments = $"\"{macOSRunInvoker.Replace("\"", "\\\"")}\" \"{fileName.Replace("\"", "\\\"")}\" {arguments}"; arguments = $"\"{macOSRunInvoker.Replace("\"", "\\\"")}\" \"{fileName.Replace("\"", "\\\"")}\" {arguments}";
fileName = node; fileName = node;

View File

@@ -1,8 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using GitHub.Runner.Sdk;
namespace GitHub.Runner.Worker.Handlers namespace GitHub.Runner.Worker.Handlers
{ {
@@ -81,22 +79,5 @@ namespace GitHub.Runner.Worker.Handlers
throw new ArgumentException($"Failed to parse COMMAND [..ARGS] from {shellOption}"); throw new ArgumentException($"Failed to parse COMMAND [..ARGS] from {shellOption}");
} }
} }
internal static string GetDefaultShellForScript(string path, Common.Tracing trace, string prependPath)
{
var format = "{0} {1}";
switch (Path.GetExtension(path))
{
case ".sh":
// use 'sh' args but prefer bash
var pathToShell = WhichUtil.Which("bash", false, trace, prependPath) ?? WhichUtil.Which("sh", true, trace, prependPath);
return string.Format(format, pathToShell, _defaultArguments["sh"]);
case ".ps1":
var pathToPowershell = WhichUtil.Which("pwsh", false, trace, prependPath) ?? WhichUtil.Which("powershell", true, trace, prependPath);
return string.Format(format, pathToPowershell, _defaultArguments["powershell"]);
default:
throw new ArgumentException($"{path} is not a valid path to a script. Make sure it ends in '.sh' or '.ps1'.");
}
}
} }
} }

View File

@@ -1,13 +1,18 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks; using System.Threading.Tasks;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Worker.Container; using GitHub.Runner.Worker.Container;
using GitHub.Services.WebApi;
using Newtonsoft.Json;
using GitHub.Runner.Common; using GitHub.Runner.Common;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using System.Linq; using System.Linq;
using GitHub.DistributedTask.Pipelines.ContextData;
namespace GitHub.Runner.Worker.Handlers namespace GitHub.Runner.Worker.Handlers
{ {

View File

@@ -15,7 +15,6 @@ using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common; using GitHub.Runner.Common;
using GitHub.Runner.Common.Util; using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using GitHub.Runner.Worker;
using Pipelines = GitHub.DistributedTask.Pipelines; using Pipelines = GitHub.DistributedTask.Pipelines;
namespace GitHub.Runner.Worker namespace GitHub.Runner.Worker
@@ -249,19 +248,6 @@ namespace GitHub.Runner.Worker
Trace.Info("Downloading actions"); Trace.Info("Downloading actions");
var actionManager = HostContext.GetService<IActionManager>(); var actionManager = HostContext.GetService<IActionManager>();
var prepareResult = await actionManager.PrepareActionsAsync(context, message.Steps); var prepareResult = await actionManager.PrepareActionsAsync(context, message.Steps);
// add hook to preJobSteps
var startedHookPath = Environment.GetEnvironmentVariable("ACTIONS_RUNNER_HOOK_JOB_STARTED");
if (!string.IsNullOrEmpty(startedHookPath))
{
var hookProvider = HostContext.GetService<IJobHookProvider>();
var jobHookData = new JobHookData(ActionRunStage.Pre, startedHookPath);
preJobSteps.Add(new JobExtensionRunner(runAsync: hookProvider.RunHook,
condition: $"{PipelineTemplateConstants.Always}()",
displayName: Constants.Hooks.JobStartedStepName,
data: (object)jobHookData));
}
preJobSteps.AddRange(prepareResult.ContainerSetupSteps); preJobSteps.AddRange(prepareResult.ContainerSetupSteps);
// Add start-container steps, record and stop-container steps // Add start-container steps, record and stop-container steps
@@ -351,18 +337,6 @@ namespace GitHub.Runner.Worker
} }
} }
// Register Job Completed hook if the variable is set
var completedHookPath = Environment.GetEnvironmentVariable("ACTIONS_RUNNER_HOOK_JOB_COMPLETED");
if (!string.IsNullOrEmpty(completedHookPath))
{
var hookProvider = HostContext.GetService<IJobHookProvider>();
var jobHookData = new JobHookData(ActionRunStage.Post, completedHookPath);
jobContext.RegisterPostJobStep(new JobExtensionRunner(runAsync: hookProvider.RunHook,
condition: $"{PipelineTemplateConstants.Always}()",
displayName: Constants.Hooks.JobCompletedStepName,
data: (object)jobHookData));
}
List<IStep> steps = new List<IStep>(); List<IStep> steps = new List<IStep>();
steps.AddRange(preJobSteps); steps.AddRange(preJobSteps);
steps.AddRange(jobSteps); steps.AddRange(jobSteps);
@@ -432,7 +406,7 @@ namespace GitHub.Runner.Worker
// create a new timeline record node for 'Finalize job' // create a new timeline record node for 'Finalize job'
IExecutionContext context = jobContext.CreateChild(Guid.NewGuid(), "Complete job", $"{nameof(JobExtension)}_Final", null, null, ActionRunStage.Post); IExecutionContext context = jobContext.CreateChild(Guid.NewGuid(), "Complete job", $"{nameof(JobExtension)}_Final", null, null, ActionRunStage.Post);
context.StepTelemetry.Type = "runner"; context.StepTelemetry.Type = "runner";
context.StepTelemetry.Action = "complete_job"; context.StepTelemetry.Action = "complete_joh";
using (var register = jobContext.CancellationToken.Register(() => { context.CancelToken(); })) using (var register = jobContext.CancellationToken.Register(() => { context.CancelToken(); }))
{ {
try try

View File

@@ -1,95 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Linq;
using GitHub.Runner.Common;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using GitHub.Runner.Worker.Handlers;
namespace GitHub.Runner.Worker
{
[ServiceLocator(Default = typeof(JobHookProvider))]
public interface IJobHookProvider : IRunnerService
{
Task RunHook(IExecutionContext executionContext, object data);
}
public class JobHookData
{
public string Path {get; private set;}
public ActionRunStage Stage {get; private set;}
public JobHookData(ActionRunStage stage, string path)
{
Path = path;
Stage = stage;
}
}
public class JobHookProvider : RunnerService, IJobHookProvider
{
public override void Initialize(IHostContext hostContext)
{
base.Initialize(hostContext);
}
public async Task RunHook(IExecutionContext executionContext, object data)
{
// Get Inputs
var hookData = data as JobHookData;
ArgUtil.NotNull(hookData, nameof(JobHookData));
var displayName = hookData.Stage == ActionRunStage.Pre ? "job started hook" : "job completed hook";
// Log to users so that they know how this step was injected
executionContext.Output($"A {displayName} has been configured by the self-hosted runner administrator");
// Validate script file.
if (!File.Exists(hookData.Path))
{
throw new FileNotFoundException("File doesn't exist");
}
executionContext.WriteWebhookPayload();
// Create the handler data.
var scriptDirectory = Path.GetDirectoryName(hookData.Path);
var stepHost = HostContext.CreateService<IDefaultStepHost>();
var prependPath = string.Join(Path.PathSeparator.ToString(), executionContext.Global.PrependPath.Reverse<string>());
Dictionary<string, string> inputs = new()
{
["path"] = hookData.Path,
["shell"] = ScriptHandlerHelpers.GetDefaultShellForScript(hookData.Path, Trace, prependPath)
};
// Create the handler
var handlerFactory = HostContext.GetService<IHandlerFactory>();
var handler = handlerFactory.Create(
executionContext,
action: null,
stepHost,
new ScriptActionExecutionData(),
inputs,
environment: new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
executionContext.Global.Variables,
actionDirectory: scriptDirectory,
localActionContainerSetupSteps: null);
handler.PrepareExecution(hookData.Stage);
// Setup file commands
var fileCommandManager = HostContext.CreateService<IFileCommandManager>();
fileCommandManager.InitializeFiles(executionContext, null);
// Run the step and process the file commands
try
{
await handler.RunAsync(hookData.Stage);
}
finally
{
fileCommandManager.ProcessFiles(executionContext, executionContext.Global.Container);
}
}
}
}

View File

@@ -6,7 +6,6 @@ using System.Net.Http;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.WebApi; using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common; using GitHub.Runner.Common;
using GitHub.Runner.Common.Util; using GitHub.Runner.Common.Util;
@@ -258,12 +257,6 @@ namespace GitHub.Runner.Worker
} }
} }
if (jobContext.JobContext.ContainsKey("Node12ActionsWarnings"))
{
var actions = string.Join(", ", jobContext.JobContext["Node12ActionsWarnings"].AssertArray("Node12ActionsWarnings").Select(action => action.ToString()));
jobContext.Warning(string.Format(Constants.Runner.Node12DetectedAfterEndOfLife, actions));
}
try try
{ {
await ShutdownQueue(throwOnFailure: true); await ShutdownQueue(throwOnFailure: true);

View File

@@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -134,10 +133,8 @@ namespace GitHub.Runner.Worker
// Test the condition again. The job was cancelled after the condition was originally evaluated. // Test the condition again. The job was cancelled after the condition was originally evaluated.
jobCancelRegister = jobContext.CancellationToken.Register(() => jobCancelRegister = jobContext.CancellationToken.Register(() =>
{ {
// Mark job as Cancelled or Failed depending on HostContext shutdown token's cancellation // Mark job as cancelled
jobContext.Result = HostContext.RunnerShutdownToken.IsCancellationRequested jobContext.Result = TaskResult.Canceled;
? TaskResult.Failed
: TaskResult.Canceled;
jobContext.JobContext.Status = jobContext.Result?.ToActionResult(); jobContext.JobContext.Status = jobContext.Result?.ToActionResult();
step.ExecutionContext.Debug($"Re-evaluate condition on job cancellation for step: '{step.DisplayName}'."); step.ExecutionContext.Debug($"Re-evaluate condition on job cancellation for step: '{step.DisplayName}'.");
@@ -175,10 +172,8 @@ namespace GitHub.Runner.Worker
{ {
if (jobContext.Result != TaskResult.Canceled) if (jobContext.Result != TaskResult.Canceled)
{ {
// Mark job as Cancelled or Failed depending on HostContext shutdown token's cancellation // Mark job as cancelled
jobContext.Result = HostContext.RunnerShutdownToken.IsCancellationRequested jobContext.Result = TaskResult.Canceled;
? TaskResult.Failed
: TaskResult.Canceled;
jobContext.JobContext.Status = jobContext.Result?.ToActionResult(); jobContext.JobContext.Status = jobContext.Result?.ToActionResult();
} }
} }
@@ -324,8 +319,29 @@ namespace GitHub.Runner.Worker
step.ExecutionContext.Result = TaskResultUtil.MergeTaskResults(step.ExecutionContext.Result, step.ExecutionContext.CommandResult.Value); step.ExecutionContext.Result = TaskResultUtil.MergeTaskResults(step.ExecutionContext.Result, step.ExecutionContext.CommandResult.Value);
} }
step.ExecutionContext.ApplyContinueOnError(step.ContinueOnError); // Fixup the step result if ContinueOnError
if (step.ExecutionContext.Result == TaskResult.Failed)
{
var continueOnError = false;
try
{
continueOnError = templateEvaluator.EvaluateStepContinueOnError(step.ContinueOnError, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions);
}
catch (Exception ex)
{
Trace.Info("The step failed and an error occurred when attempting to determine whether to continue on error.");
Trace.Error(ex);
step.ExecutionContext.Error("The step failed and an error occurred when attempting to determine whether to continue on error.");
step.ExecutionContext.Error(ex);
}
if (continueOnError)
{
step.ExecutionContext.Outcome = step.ExecutionContext.Result;
step.ExecutionContext.Result = TaskResult.Succeeded;
Trace.Info($"Updated step result (continue on error)");
}
}
Trace.Info($"Step result: {step.ExecutionContext.Result}"); Trace.Info($"Step result: {step.ExecutionContext.Result}");
// Complete the step context // Complete the step context

View File

@@ -129,10 +129,9 @@
"required": true "required": true
}, },
"env": "step-env", "env": "step-env",
"continue-on-error": "boolean-steps-context",
"working-directory": "string-steps-context", "working-directory": "string-steps-context",
"shell": { "shell": {
"type": "string-steps-context", "type": "non-empty-string",
"required": true "required": true
} }
} }
@@ -148,7 +147,6 @@
"type": "non-empty-string", "type": "non-empty-string",
"required": true "required": true
}, },
"continue-on-error": "boolean-steps-context",
"with": "step-with", "with": "step-with",
"env": "step-env" "env": "step-env"
} }
@@ -203,20 +201,6 @@
], ],
"string": {} "string": {}
}, },
"boolean-steps-context": {
"context": [
"github",
"inputs",
"strategy",
"matrix",
"steps",
"job",
"runner",
"env",
"hashFiles(1,255)"
],
"boolean": {}
},
"step-env": { "step-env": {
"context": [ "context": [
"github", "github",

View File

@@ -708,85 +708,33 @@ namespace GitHub.Runner.Common.Tests
} }
} }
[Theory] [Fact]
[InlineData("configure", "once")]
[InlineData("remove", "disableupdate")]
[InlineData("remove", "ephemeral")]
[InlineData("remove", "once")]
[InlineData("remove", "replace")]
[InlineData("remove", "runasservice")]
[InlineData("remove", "unattended")]
[InlineData("run", "disableupdate")]
[InlineData("run", "ephemeral")]
[InlineData("run", "replace")]
[InlineData("run", "runasservice")]
[InlineData("run", "unattended")]
[InlineData("warmup", "disableupdate")]
[InlineData("warmup", "ephemeral")]
[InlineData("warmup", "once")]
[InlineData("warmup", "replace")]
[InlineData("warmup", "runasservice")]
[InlineData("warmup", "unattended")]
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))] [Trait("Category", nameof(CommandSettings))]
public void ValidateInvalidFlagCommandCombination(string validCommand, string flag) public void ValidateFlags()
{ {
using (TestHostContext hc = CreateTestContext()) using (TestHostContext hc = CreateTestContext())
{ {
// Arrange. // Arrange.
var command = new CommandSettings(hc, args: new string[] { validCommand, $"--{flag}" }); var command = new CommandSettings(hc, args: new string[] { "--badflag" });
// Assert. // Assert.
Assert.Contains(flag, command.Validate()); Assert.Contains("badflag", command.Validate());
} }
} }
[Theory] [Fact]
[InlineData("remove", "auth", "bar arg value")]
[InlineData("remove", "labels", "bar arg value")]
[InlineData("remove", "monitorsocketaddress", "bar arg value")]
[InlineData("remove", "name", "bar arg value")]
[InlineData("remove", "runnergroup", "bar arg value")]
[InlineData("remove", "url", "bar arg value")]
[InlineData("remove", "username", "bar arg value")]
[InlineData("remove", "windowslogonaccount", "bar arg value")]
[InlineData("remove", "windowslogonpassword", "bar arg value")]
[InlineData("remove", "work", "bar arg value")]
[InlineData("run", "auth", "bad arg value")]
[InlineData("run", "labels", "bad arg value")]
[InlineData("run", "monitorsocketaddress", "bad arg value")]
[InlineData("run", "name", "bad arg value")]
[InlineData("run", "pat", "bad arg value")]
[InlineData("run", "runnergroup", "bad arg value")]
[InlineData("run", "token", "bad arg value")]
[InlineData("run", "url", "bad arg value")]
[InlineData("run", "username", "bad arg value")]
[InlineData("run", "windowslogonaccount", "bad arg value")]
[InlineData("run", "windowslogonpassword", "bad arg value")]
[InlineData("run", "work", "bad arg value")]
[InlineData("warmup", "auth", "bad arg value")]
[InlineData("warmup", "labels", "bad arg value")]
[InlineData("warmup", "monitorsocketaddress", "bad arg value")]
[InlineData("warmup", "name", "bad arg value")]
[InlineData("warmup", "pat", "bad arg value")]
[InlineData("warmup", "runnergroup", "bad arg value")]
[InlineData("warmup", "token", "bad arg value")]
[InlineData("warmup", "url", "bad arg value")]
[InlineData("warmup", "username", "bad arg value")]
[InlineData("warmup", "windowslogonaccount", "bad arg value")]
[InlineData("warmup", "windowslogonpassword", "bad arg value")]
[InlineData("warmup", "work", "bad arg value")]
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))] [Trait("Category", nameof(CommandSettings))]
public void ValidateInvalidArgCommandCombination(string validCommand, string arg, string argValue) public void ValidateArgs()
{ {
using (TestHostContext hc = CreateTestContext()) using (TestHostContext hc = CreateTestContext())
{ {
// Arrange. // Arrange.
var command = new CommandSettings(hc, args: new string[] { validCommand, $"--{arg}", argValue }); var command = new CommandSettings(hc, args: new string[] { "--badargname", "bad arg value" });
// Assert. // Assert.
Assert.Contains(arg, command.Validate()); Assert.Contains("badargname", command.Validate());
} }
} }
@@ -810,73 +758,6 @@ namespace GitHub.Runner.Common.Tests
} }
} }
[Theory]
[InlineData("configure", "help")]
[InlineData("configure", "version")]
[InlineData("configure", "commit")]
[InlineData("configure", "check")]
[InlineData("configure", "disableupdate")]
[InlineData("configure", "ephemeral")]
[InlineData("configure", "replace")]
[InlineData("configure", "runasservice")]
[InlineData("configure", "unattended")]
[InlineData("remove", "help")]
[InlineData("remove", "version")]
[InlineData("remove", "commit")]
[InlineData("remove", "check")]
[InlineData("run", "help")]
[InlineData("run", "version")]
[InlineData("run", "commit")]
[InlineData("run", "check")]
[InlineData("run", "once")]
[InlineData("warmup", "help")]
[InlineData("warmup", "version")]
[InlineData("warmup", "commit")]
[InlineData("warmup", "check")]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void ValidateGoodFlagCommandCombination(string validCommand, string flag)
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[] { validCommand, $"--{flag}" });
// Assert.
Assert.True(command.Validate().Count == 0);
}
}
[Theory]
[InlineData("configure", "auth", "good arg value")]
[InlineData("configure", "labels", "good arg value")]
[InlineData("configure", "monitorsocketaddress", "good arg value")]
[InlineData("configure", "name", "good arg value")]
[InlineData("configure", "pat", "good arg value")]
[InlineData("configure", "runnergroup", "good arg value")]
[InlineData("configure", "token", "good arg value")]
[InlineData("configure", "url", "good arg value")]
[InlineData("configure", "username", "good arg value")]
[InlineData("configure", "windowslogonaccount", "good arg value")]
[InlineData("configure", "windowslogonpassword", "good arg value")]
[InlineData("configure", "work", "good arg value")]
[InlineData("remove", "token", "good arg value")]
[InlineData("remove", "pat", "good arg value")]
[InlineData("run", "startuptype", "good arg value")]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void ValidateGoodArgCommandCombination(string validCommand, string arg, string argValue)
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[] { validCommand, $"--{arg}", argValue });
// Assert.
Assert.True(command.Validate().Count == 0);
}
}
private TestHostContext CreateTestContext([CallerMemberName] string testName = "") private TestHostContext CreateTestContext([CallerMemberName] string testName = "")
{ {
TestHostContext hc = new TestHostContext(this, testName); TestHostContext hc = new TestHostContext(this, testName);

View File

@@ -1,6 +1,5 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using GitHub.Runner.Common; using GitHub.Runner.Common;
using GitHub.Runner.Listener.Configuration; using GitHub.Runner.Listener.Configuration;
using Xunit; using Xunit;
@@ -16,7 +15,7 @@ namespace GitHub.Runner.Common.Tests
{ {
RunnerSettings settings = new RunnerSettings(); RunnerSettings settings = new RunnerSettings();
settings.AgentName = "thisiskindofalongrunnerabcde"; settings.AgentName = "thisiskindofalongrunnername1";
settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890"; settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890";
settings.GitHubUrl = "https://github.com/myorganizationexample/myrepoexample"; settings.GitHubUrl = "https://github.com/myorganizationexample/myrepoexample";
@@ -44,7 +43,7 @@ namespace GitHub.Runner.Common.Tests
Assert.Equal("actions", serviceNameParts[0]); Assert.Equal("actions", serviceNameParts[0]);
Assert.Equal("runner", serviceNameParts[1]); Assert.Equal("runner", serviceNameParts[1]);
Assert.Equal("myorganizationexample-myrepoexample", serviceNameParts[2]); // '/' has been replaced with '-' Assert.Equal("myorganizationexample-myrepoexample", serviceNameParts[2]); // '/' has been replaced with '-'
Assert.Equal("thisiskindofalongrunnerabcde", serviceNameParts[3]); Assert.Equal("thisiskindofalongrunnername1", serviceNameParts[3]);
} }
} }
@@ -55,7 +54,7 @@ namespace GitHub.Runner.Common.Tests
{ {
RunnerSettings settings = new RunnerSettings(); RunnerSettings settings = new RunnerSettings();
settings.AgentName = "thisiskindofalongrunnernabcde"; settings.AgentName = "thisiskindofalongrunnername12";
settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890"; settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890";
settings.GitHubUrl = "https://github.com/myorganizationexample/myrepoexample"; settings.GitHubUrl = "https://github.com/myorganizationexample/myrepoexample";
@@ -83,129 +82,88 @@ namespace GitHub.Runner.Common.Tests
Assert.Equal("actions", serviceNameParts[0]); Assert.Equal("actions", serviceNameParts[0]);
Assert.Equal("runner", serviceNameParts[1]); Assert.Equal("runner", serviceNameParts[1]);
Assert.Equal("myorganizationexample-myrepoexample", serviceNameParts[2]); // '/' has been replaced with '-' Assert.Equal("myorganizationexample-myrepoexample", serviceNameParts[2]); // '/' has been replaced with '-'
Assert.Equal("thisiskindofalongrunnernabcde", serviceNameParts[3]); // should not have random numbers unless we exceed 80 Assert.Equal("thisiskindofalongrunnername12", serviceNameParts[3]);
} }
} }
#if OS_WINDOWS [Fact]
[Fact] [Trait("Level", "L0")]
[Trait("Level", "L0")] [Trait("Category", "Service")]
[Trait("Category", "Service")] public void CalculateServiceNameLimitsServiceNameTo80Chars()
public void CalculateServiceNameLimitsServiceNameTo80Chars() {
RunnerSettings settings = new RunnerSettings();
settings.AgentName = "thisisareallyreallylongbutstillvalidagentname";
settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890";
settings.GitHubUrl = "https://github.com/myreallylongorganizationexample/myreallylongrepoexample";
string serviceNamePattern = "actions.runner.{0}.{1}";
string serviceDisplayNamePattern = "GitHub Actions Runner ({0}.{1})";
using (TestHostContext hc = CreateTestContext())
{ {
RunnerSettings settings = new RunnerSettings(); ServiceControlManager scm = new ServiceControlManager();
settings.AgentName = "thisisareallyreallylongbutstillvalidagentname"; scm.Initialize(hc);
settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890"; scm.CalculateServiceName(
settings.GitHubUrl = "https://github.com/myreallylongorganizationexample/myreallylongrepoexample"; settings,
serviceNamePattern,
serviceDisplayNamePattern,
out string serviceName,
out string serviceDisplayName);
string serviceNamePattern = "actions.runner.{0}.{1}"; // Verify name has been shortened to 80 characters
string serviceDisplayNamePattern = "GitHub Actions Runner ({0}.{1})"; Assert.Equal(80, serviceName.Length);
using (TestHostContext hc = CreateTestContext()) var serviceNameParts = serviceName.Split('.');
{
ServiceControlManager scm = new ServiceControlManager();
scm.Initialize(hc); // Verify that each component has been shortened to a sensible length
scm.CalculateServiceName( Assert.Equal("actions", serviceNameParts[0]); // Never shortened
settings, Assert.Equal("runner", serviceNameParts[1]); // Never shortened
serviceNamePattern, Assert.Equal("myreallylongorganizationexample-myreallylongr", serviceNameParts[2]); // First 45 chars, '/' has been replaced with '-'
serviceDisplayNamePattern, Assert.Equal("thisisareallyreally", serviceNameParts[3]); // Remainder of unused chars
out string serviceName,
out string serviceDisplayName);
// Verify name has been shortened to 80 characters
Assert.Equal(80, serviceName.Length);
var serviceNameParts = serviceName.Split('.');
// Verify that each component has been shortened to a sensible length
Assert.Equal("actions", serviceNameParts[0]); // Never shortened
Assert.Equal("runner", serviceNameParts[1]); // Never shortened
Assert.Equal("myreallylongorganizationexample-myreallylongr", serviceNameParts[2]); // First 45 chars, '/' has been replaced with '-'
Assert.Matches(@"^(thisisareallyr-[0-9]{4})$", serviceNameParts[3]); // Remainder of unused chars, 4 random numbers added at the end
}
} }
}
// Special 'defensive' test that verifies we can gracefully handle creating service names // Special 'defensive' test that verifies we can gracefully handle creating service names
// in case GitHub.com changes its org/repo naming convention in the future, // in case GitHub.com changes its org/repo naming convention in the future,
// and some of these characters may be invalid for service names // and some of these characters may be invalid for service names
// Not meant to test character set exhaustively -- it's just here to exercise the sanitizing logic // Not meant to test character set exhaustively -- it's just here to exercise the sanitizing logic
[Fact] [Fact]
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", "Service")] [Trait("Category", "Service")]
public void CalculateServiceNameSanitizeOutOfRangeChars() public void CalculateServiceNameSanitizeOutOfRangeChars()
{
RunnerSettings settings = new RunnerSettings();
settings.AgentName = "name";
settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890";
settings.GitHubUrl = "https://github.com/org!@$*+[]()/repo!@$*+[]()";
string serviceNamePattern = "actions.runner.{0}.{1}";
string serviceDisplayNamePattern = "GitHub Actions Runner ({0}.{1})";
using (TestHostContext hc = CreateTestContext())
{ {
RunnerSettings settings = new RunnerSettings(); ServiceControlManager scm = new ServiceControlManager();
settings.AgentName = "name"; scm.Initialize(hc);
settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890"; scm.CalculateServiceName(
settings.GitHubUrl = "https://github.com/org!@$*+[]()/repo!@$*+[]()"; settings,
serviceNamePattern,
serviceDisplayNamePattern,
out string serviceName,
out string serviceDisplayName);
string serviceNamePattern = "actions.runner.{0}.{1}"; var serviceNameParts = serviceName.Split('.');
string serviceDisplayNamePattern = "GitHub Actions Runner ({0}.{1})";
using (TestHostContext hc = CreateTestContext()) // Verify service name parts are sanitized correctly
{ Assert.Equal("actions", serviceNameParts[0]);
ServiceControlManager scm = new ServiceControlManager(); Assert.Equal("runner", serviceNameParts[1]);
Assert.Equal("org----------repo---------", serviceNameParts[2]); // Chars replaced with '-'
scm.Initialize(hc); Assert.Equal("name", serviceNameParts[3]);
scm.CalculateServiceName(
settings,
serviceNamePattern,
serviceDisplayNamePattern,
out string serviceName,
out string serviceDisplayName);
var serviceNameParts = serviceName.Split('.');
// Verify service name parts are sanitized correctly
Assert.Equal("actions", serviceNameParts[0]);
Assert.Equal("runner", serviceNameParts[1]);
Assert.Equal("org----------repo---------", serviceNameParts[2]); // Chars replaced with '-'
Assert.Equal("name", serviceNameParts[3]);
}
} }
#else }
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Service")]
public void CalculateServiceNameLimitsServiceNameTo150Chars()
{
RunnerSettings settings = new RunnerSettings();
settings.AgentName = "thisisareallyreallylongbutstillvalidagentnameiamusingforthisexampletotestverylongnamelimits";
settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890";
settings.GitHubUrl = "https://github.com/myreallylongorganizationexampleonlinux/myreallylongrepoexampleonlinux1234";
string serviceNamePattern = "actions.runner.{0}.{1}";
string serviceDisplayNamePattern = "GitHub Actions Runner ({0}.{1})";
using (TestHostContext hc = CreateTestContext())
{
ServiceControlManager scm = new ServiceControlManager();
scm.Initialize(hc);
scm.CalculateServiceName(
settings,
serviceNamePattern,
serviceDisplayNamePattern,
out string serviceName,
out string serviceDisplayName);
// Verify name has been shortened to 150
Assert.Equal(150, serviceName.Length);
var serviceNameParts = serviceName.Split('.');
// Verify that each component has been shortened to a sensible length
Assert.Equal("actions", serviceNameParts[0]); // Never shortened
Assert.Equal("runner", serviceNameParts[1]); // Never shortened
Assert.Equal("myreallylongorganizationexampleonlinux-myreallylongrepoexampleonlinux1", serviceNameParts[2]); // First 70 chars, '/' has been replaced with '-'
Assert.Matches(@"^(thisisareallyreallylongbutstillvalidagentnameiamusingforthi-[0-9]{4})$", serviceNameParts[3]);
}
}
#endif
private TestHostContext CreateTestContext([CallerMemberName] string testName = "") private TestHostContext CreateTestContext([CallerMemberName] string testName = "")
{ {

View File

@@ -118,7 +118,7 @@ namespace GitHub.Runner.Common.Tests.Worker
await _actionRunner.RunAsync(); await _actionRunner.RunAsync();
//Assert //Assert
_ec.Verify(x => x.WriteWebhookPayload(), Times.Once); _ec.Verify(x => x.SetGitHubContext("event_path", Path.Combine(_hc.GetDirectory(WellKnownDirectory.Temp), "_github_workflow", "event.json")), Times.Once);
} }
[Fact] [Fact]

View File

@@ -6,11 +6,8 @@ using System.Threading;
using GitHub.DistributedTask.Pipelines.ContextData; using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.WebApi; using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Worker; using GitHub.Runner.Worker;
using GitHub.Runner.Worker.Container;
using GitHub.Runner.Worker.Handlers;
using Moq; using Moq;
using Xunit; using Xunit;
using GitHub.DistributedTask.ObjectTemplating.Tokens;
using Pipelines = GitHub.DistributedTask.Pipelines; using Pipelines = GitHub.DistributedTask.Pipelines;
namespace GitHub.Runner.Common.Tests.Worker namespace GitHub.Runner.Common.Tests.Worker
@@ -93,63 +90,6 @@ namespace GitHub.Runner.Common.Tests.Worker
} }
} }
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void ApplyContinueOnError_CheckResultAndOutcome()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange: Create a job request message.
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
TimelineReference timeline = new TimelineReference();
Guid jobId = Guid.NewGuid();
string jobName = "some job name";
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
{
Alias = Pipelines.PipelineConstants.SelfAlias,
Id = "github",
Version = "sha1"
});
jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
jobRequest.Variables["ACTIONS_STEP_DEBUG"] = "true";
// Arrange: Setup the paging logger.
var pagingLogger = new Mock<IPagingLogger>();
var jobServerQueue = new Mock<IJobServerQueue>();
jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.IsAny<TimelineRecord>()));
jobServerQueue.Setup(x => x.QueueWebConsoleLine(It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<long>())).Callback((Guid id, string msg, long? lineNumber) => { hc.GetTrace().Info(msg); });
hc.EnqueueInstance(pagingLogger.Object);
hc.SetSingleton(jobServerQueue.Object);
var ec = new Runner.Worker.ExecutionContext();
ec.Initialize(hc);
// Act.
ec.InitializeJob(jobRequest, CancellationToken.None);
foreach (var tc in new List<(TemplateToken token, TaskResult result, TaskResult? expectedResult, TaskResult? expectedOutcome)> {
(token: new BooleanToken(null, null, null, true), result: TaskResult.Failed, expectedResult: TaskResult.Succeeded, expectedOutcome: TaskResult.Failed),
(token: new BooleanToken(null, null, null, true), result: TaskResult.Succeeded, expectedResult: TaskResult.Succeeded, expectedOutcome: null),
(token: new BooleanToken(null, null, null, true), result: TaskResult.Canceled, expectedResult: TaskResult.Canceled, expectedOutcome: null),
(token: new BooleanToken(null, null, null, false), result: TaskResult.Failed, expectedResult: TaskResult.Failed, expectedOutcome: null),
(token: new BooleanToken(null, null, null, false), result: TaskResult.Succeeded, expectedResult: TaskResult.Succeeded, expectedOutcome: null),
(token: new BooleanToken(null, null, null, false), result: TaskResult.Canceled, expectedResult: TaskResult.Canceled, expectedOutcome: null),
})
{
ec.Result = tc.result;
ec.Outcome = null;
ec.ApplyContinueOnError(tc.token);
Assert.Equal(ec.Result, tc.expectedResult);
Assert.Equal(ec.Outcome, tc.expectedOutcome);
}
}
}
[Fact] [Fact]
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", "Worker")] [Trait("Category", "Worker")]
@@ -204,55 +144,6 @@ namespace GitHub.Runner.Common.Tests.Worker
} }
} }
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void AddIssue_AddStepAndLineNumberInformation()
{
using (TestHostContext hc = CreateTestContext())
{
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
TimelineReference timeline = new TimelineReference();
Guid jobId = Guid.NewGuid();
string jobName = "some job name";
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
{
Alias = Pipelines.PipelineConstants.SelfAlias,
Id = "github",
Version = "sha1"
});
jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
// Arrange: Setup the paging logger.
var pagingLogger = new Mock<IPagingLogger>();
var pagingLogger2 = new Mock<IPagingLogger>();
var jobServerQueue = new Mock<IJobServerQueue>();
jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.IsAny<TimelineRecord>()));
hc.EnqueueInstance(pagingLogger.Object);
hc.EnqueueInstance(pagingLogger2.Object);
hc.SetSingleton(jobServerQueue.Object);
var ec = new Runner.Worker.ExecutionContext();
ec.Initialize(hc);
ec.InitializeJob(jobRequest, CancellationToken.None);
ec.Start();
var embeddedStep = ec.CreateChild(Guid.NewGuid(), "action_1_pre", "action_1_pre", null, null, ActionRunStage.Main, isEmbedded: true);
embeddedStep.Start();
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.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);
}
}
[Fact] [Fact]
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", "Worker")] [Trait("Category", "Worker")]
@@ -726,149 +617,5 @@ namespace GitHub.Runner.Common.Tests.Worker
return hc; return hc;
} }
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void GetExpressionValues_ContainerStepHost()
{
using (TestHostContext hc = CreateTestContext())
{
const string source = "/home/username/Projects/work/runner/_layout";
var containerInfo = new ContainerInfo();
containerInfo.ContainerId = "test";
containerInfo.AddPathTranslateMapping($"{source}/_work", "/__w");
containerInfo.AddPathTranslateMapping($"{source}/_temp", "/__t");
containerInfo.AddPathTranslateMapping($"{source}/externals", "/__e");
containerInfo.AddPathTranslateMapping($"{source}/_work/_temp/_github_home", "/github/home");
containerInfo.AddPathTranslateMapping($"{source}/_work/_temp/_github_workflow", "/github/workflow");
foreach (var v in new List<string>() {
$"{source}/_work",
$"{source}/externals",
$"{source}/_work/_temp",
$"{source}/_work/_actions",
$"{source}/_work/_tool",
})
{
containerInfo.MountVolumes.Add(new MountVolume(v, containerInfo.TranslateToContainerPath(v)));
};
var stepHost = new ContainerStepHost();
stepHost.Container = containerInfo;
var ec = new Runner.Worker.ExecutionContext();
ec.Initialize(hc);
var inputGithubContext = new GitHubContext();
var inputeRunnerContext = new RunnerContext();
// string context data
inputGithubContext["action_path"] = new StringContextData("/home/username/Projects/work/runner/_layout/_work/_actions/owner/composite/main");
inputGithubContext["action"] = new StringContextData("__owner_composite");
inputGithubContext["api_url"] = new StringContextData("https://api.github.com/custom/path");
inputGithubContext["env"] = new StringContextData("/home/username/Projects/work/runner/_layout/_work/_temp/_runner_file_commands/set_env_265698aa-7f38-40f5-9316-5c01a3153672");
inputGithubContext["path"] = new StringContextData("/home/username/Projects/work/runner/_layout/_work/_temp/_runner_file_commands/add_path_265698aa-7f38-40f5-9316-5c01a3153672");
inputGithubContext["event_path"] = new StringContextData("/home/username/Projects/work/runner/_layout/_work/_temp/_github_workflow/event.json");
inputGithubContext["repository"] = new StringContextData("owner/repo-name");
inputGithubContext["run_id"] = new StringContextData("2033211332");
inputGithubContext["workflow"] = new StringContextData("Name of Workflow");
inputGithubContext["workspace"] = new StringContextData("/home/username/Projects/work/runner/_layout/_work/step-order/step-order");
inputeRunnerContext["temp"] = new StringContextData("/home/username/Projects/work/runner/_layout/_work/_temp");
inputeRunnerContext["tool_cache"] = new StringContextData("/home/username/Projects/work/runner/_layout/_work/_tool");
// dictionary context data
var githubEvent = new DictionaryContextData();
githubEvent["inputs"] = null;
githubEvent["ref"] = new StringContextData("refs/heads/main");
githubEvent["repository"] = new DictionaryContextData();
githubEvent["sender"] = new DictionaryContextData();
githubEvent["workflow"] = new StringContextData(".github/workflows/composite_step_host_translate.yaml");
inputGithubContext["event"] = githubEvent;
ec.ExpressionValues["github"] = inputGithubContext;
ec.ExpressionValues["runner"] = inputeRunnerContext;
var ecExpect = new Runner.Worker.ExecutionContext();
ecExpect.Initialize(hc);
var expectedGithubEvent = new DictionaryContextData();
expectedGithubEvent["inputs"] = null;
expectedGithubEvent["ref"] = new StringContextData("refs/heads/main");
expectedGithubEvent["repository"] = new DictionaryContextData();
expectedGithubEvent["sender"] = new DictionaryContextData();
expectedGithubEvent["workflow"] = new StringContextData(".github/workflows/composite_step_host_translate.yaml");
var expectedGithubContext = new GitHubContext();
var expectedRunnerContext = new RunnerContext();
expectedGithubContext["action_path"] = new StringContextData("/__w/_actions/owner/composite/main");
expectedGithubContext["action"] = new StringContextData("__owner_composite");
expectedGithubContext["api_url"] = new StringContextData("https://api.github.com/custom/path");
expectedGithubContext["env"] = new StringContextData("/__w/_temp/_runner_file_commands/set_env_265698aa-7f38-40f5-9316-5c01a3153672");
expectedGithubContext["path"] = new StringContextData("/__w/_temp/_runner_file_commands/add_path_265698aa-7f38-40f5-9316-5c01a3153672");
expectedGithubContext["event_path"] = new StringContextData("/github/workflow/event.json");
expectedGithubContext["repository"] = new StringContextData("owner/repo-name");
expectedGithubContext["run_id"] = new StringContextData("2033211332");
expectedGithubContext["workflow"] = new StringContextData("Name of Workflow");
expectedGithubContext["workspace"] = new StringContextData("/__w/step-order/step-order");
expectedGithubContext["event"] = expectedGithubEvent;
expectedRunnerContext["temp"] = new StringContextData("/__w/_temp");
expectedRunnerContext["tool_cache"] = new StringContextData("/__w/_tool");
ecExpect.ExpressionValues["github"] = expectedGithubContext;
ecExpect.ExpressionValues["runner"] = expectedRunnerContext;
var translatedExpressionValues = ec.GetExpressionValues(stepHost);
foreach (var contextName in new string[] { "github", "runner" })
{
var dict = translatedExpressionValues[contextName].AssertDictionary($"expected context github to be a dictionary");
var expectedExpressionValues = ecExpect.ExpressionValues[contextName].AssertDictionary("expect dict");
foreach (var key in dict.Keys.ToList())
{
if (dict[key] is StringContextData)
{
var expect = dict[key].AssertString("expect string");
var outcome = expectedExpressionValues[key].AssertString("expect string");
Assert.Equal(expect.Value, outcome.Value);
}
else if (dict[key] is DictionaryContextData || dict[key] is CaseSensitiveDictionaryContextData)
{
var expectDict = dict[key].AssertDictionary("expect dict");
var actualDict = expectedExpressionValues[key].AssertDictionary("expect dict");
Assert.True(ExpressionValuesAssertEqual(expectDict, actualDict));
}
}
}
}
}
private bool ExpressionValuesAssertEqual(DictionaryContextData expect, DictionaryContextData actual)
{
foreach (var key in expect.Keys.ToList())
{
if (expect[key] is StringContextData)
{
var expectValue = expect[key].AssertString("expect string");
var actualValue = actual[key].AssertString("expect string");
if (expectValue.Equals(actualValue))
{
return false;
}
}
else if (expect[key] is DictionaryContextData || expect[key] is CaseSensitiveDictionaryContextData)
{
var expectDict = expect[key].AssertDictionary("expect dict");
var actualDict = actual[key].AssertDictionary("expect dict");
if (!ExpressionValuesAssertEqual(expectDict, actualDict))
{
return false;
}
}
}
return true;
}
} }
} }

View File

@@ -1,100 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Moq;
using Xunit;
using GitHub.Runner.Worker;
using GitHub.Runner.Worker.Handlers;
using GitHub.DistributedTask.Pipelines;
using GitHub.DistributedTask.WebApi;
namespace GitHub.Runner.Common.Tests.Worker
{
public sealed class HandlerFactoryL0
{
private Mock<IExecutionContext> _ec;
private TestHostContext CreateTestContext([CallerMemberName] string testName = "")
{
var hostContext = new TestHostContext(this, testName);
_ec = new Mock<IExecutionContext>();
_ec.SetupAllProperties();
_ec.Object.Initialize(hostContext);
var handler = new Mock<INodeScriptActionHandler>();
handler.SetupAllProperties();
hostContext.EnqueueInstance(handler.Object);
//hostContext.EnqueueInstance(new ActionCommandManager() as IActionCommandManager);
return hostContext;
}
[Theory]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
[InlineData("node12", "", "", "", "node12")]
[InlineData("node12", "true", "", "", "node16")]
[InlineData("node12", "true", "", "true", "node12")]
[InlineData("node12", "true", "true", "", "node12")]
[InlineData("node12", "true", "true", "true", "node12")]
[InlineData("node12", "true", "false", "true", "node16")] // workflow overrides env
[InlineData("node16", "", "", "", "node16")]
[InlineData("node16", "true", "", "", "node16")]
[InlineData("node16", "true", "", "true", "node16")]
[InlineData("node16", "true", "true", "", "node16")]
[InlineData("node16", "true", "true", "true", "node16")]
[InlineData("node16", "true", "false", "true", "node16")]
public void IsNodeVersionUpgraded(string inputVersion, string serverFeatureFlag, string workflowOptOut, string machineOptOut, string expectedVersion)
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var hf = new HandlerFactory();
hf.Initialize(hc);
// Server Feature Flag
var variables = new Dictionary<string, VariableValue>();
if (!string.IsNullOrEmpty(serverFeatureFlag))
{
variables["DistributedTask.ForceGithubJavascriptActionsToNode16"] = serverFeatureFlag;
}
Variables serverVariables = new Variables(hc, variables);
// Workflow opt-out
var workflowVariables = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(workflowOptOut))
{
workflowVariables[Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion] = workflowOptOut;
}
// Machine opt-out
if (!string.IsNullOrEmpty(machineOptOut))
{
Environment.SetEnvironmentVariable(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion, machineOptOut);
}
_ec.Setup(x => x.Global).Returns(new GlobalContext()
{
Variables = serverVariables,
EnvironmentVariables = workflowVariables
});
// Act.
var data = new NodeJSActionExecutionData();
data.NodeVersion = inputVersion;
var handler = hf.Create(
_ec.Object,
new ScriptReference(),
new Mock<IStepHost>().Object,
data,
new Dictionary<string, string>(),
new Dictionary<string, string>(),
new Variables(hc, new Dictionary<string, VariableValue>()), "", new List<JobExtensionRunner>()
) as INodeScriptActionHandler;
// Assert.
Assert.Equal(expectedVersion, handler.Data.NodeVersion);
Environment.SetEnvironmentVariable(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion, null);
}
}
}
}

View File

@@ -25,7 +25,6 @@ namespace GitHub.Runner.Common.Tests.Worker
private Mock<IPagingLogger> _logger; private Mock<IPagingLogger> _logger;
private Mock<IContainerOperationProvider> _containerProvider; private Mock<IContainerOperationProvider> _containerProvider;
private Mock<IDiagnosticLogManager> _diagnosticLogManager; private Mock<IDiagnosticLogManager> _diagnosticLogManager;
private Mock<IJobHookProvider> _jobHookProvider;
private CancellationTokenSource _tokenSource; private CancellationTokenSource _tokenSource;
private TestHostContext CreateTestContext([CallerMemberName] String testName = "") private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
@@ -41,7 +40,6 @@ namespace GitHub.Runner.Common.Tests.Worker
_directoryManager = new Mock<IPipelineDirectoryManager>(); _directoryManager = new Mock<IPipelineDirectoryManager>();
_directoryManager.Setup(x => x.PrepareDirectory(It.IsAny<IExecutionContext>(), It.IsAny<Pipelines.WorkspaceOptions>())) _directoryManager.Setup(x => x.PrepareDirectory(It.IsAny<IExecutionContext>(), It.IsAny<Pipelines.WorkspaceOptions>()))
.Returns(new TrackingConfig() { PipelineDirectory = "runner", WorkspaceDirectory = "runner/runner" }); .Returns(new TrackingConfig() { PipelineDirectory = "runner", WorkspaceDirectory = "runner/runner" });
_jobHookProvider = new Mock<IJobHookProvider>();
IActionRunner step1 = new ActionRunner(); IActionRunner step1 = new ActionRunner();
IActionRunner step2 = new ActionRunner(); IActionRunner step2 = new ActionRunner();
@@ -113,9 +111,7 @@ namespace GitHub.Runner.Common.Tests.Worker
hc.SetSingleton(_containerProvider.Object); hc.SetSingleton(_containerProvider.Object);
hc.SetSingleton(_directoryManager.Object); hc.SetSingleton(_directoryManager.Object);
hc.SetSingleton(_diagnosticLogManager.Object); hc.SetSingleton(_diagnosticLogManager.Object);
hc.SetSingleton(_jobHookProvider.Object);
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // JobExecutionContext hc.EnqueueInstance<IPagingLogger>(_logger.Object); // JobExecutionContext
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // job start hook
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // Initial Job hc.EnqueueInstance<IPagingLogger>(_logger.Object); // Initial Job
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // step1 hc.EnqueueInstance<IPagingLogger>(_logger.Object); // step1
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // step2 hc.EnqueueInstance<IPagingLogger>(_logger.Object); // step2
@@ -124,7 +120,6 @@ namespace GitHub.Runner.Common.Tests.Worker
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // step5 hc.EnqueueInstance<IPagingLogger>(_logger.Object); // step5
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // prepare1 hc.EnqueueInstance<IPagingLogger>(_logger.Object); // prepare1
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // prepare2 hc.EnqueueInstance<IPagingLogger>(_logger.Object); // prepare2
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // job complete hook
hc.EnqueueInstance<IActionRunner>(step1); hc.EnqueueInstance<IActionRunner>(step1);
hc.EnqueueInstance<IActionRunner>(step2); hc.EnqueueInstance<IActionRunner>(step2);
@@ -353,62 +348,5 @@ namespace GitHub.Runner.Common.Tests.Worker
Assert.Equal(TaskResult.Succeeded, _jobEc.Result); Assert.Equal(TaskResult.Succeeded, _jobEc.Result);
} }
} }
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public async Task EnsurePreAndPostHookStepsIfEnvExists()
{
Environment.SetEnvironmentVariable("ACTIONS_RUNNER_HOOK_JOB_STARTED", "/foo/bar");
Environment.SetEnvironmentVariable("ACTIONS_RUNNER_HOOK_JOB_COMPLETED", "/bar/foo");
using (TestHostContext hc = CreateTestContext())
{
var jobExtension = new JobExtension();
jobExtension.Initialize(hc);
_actionManager.Setup(x => x.PrepareActionsAsync(It.IsAny<IExecutionContext>(), It.IsAny<IEnumerable<Pipelines.JobStep>>(), It.IsAny<Guid>()))
.Returns(Task.FromResult(new PrepareResult(new List<JobExtensionRunner>(), new Dictionary<Guid, IActionRunner>())));
List<IStep> result = await jobExtension.InitializeJob(_jobEc, _message);
var trace = hc.GetTrace();
var hookStart = result.First() as JobExtensionRunner;
jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow);
Assert.Equal(Constants.Hooks.JobStartedStepName, hookStart.DisplayName);
Assert.Equal(Constants.Hooks.JobCompletedStepName, (_jobEc.PostJobSteps.Last() as JobExtensionRunner).DisplayName);
}
Environment.SetEnvironmentVariable("ACTIONS_RUNNER_HOOK_JOB_STARTED", null);
Environment.SetEnvironmentVariable("ACTIONS_RUNNER_HOOK_JOB_COMPLETED", null);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void EnsureNoPreAndPostHookSteps()
{
using (TestHostContext hc = CreateTestContext())
{
var jobExtension = new JobExtension();
jobExtension.Initialize(hc);
_message.ActionsEnvironment = null;
_jobEc = new Runner.Worker.ExecutionContext {Result = TaskResult.Succeeded};
_jobEc.Initialize(hc);
_jobEc.InitializeJob(_message, _tokenSource.Token);
var x = _jobEc.JobSteps;
jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow);
Assert.Equal(TaskResult.Succeeded, _jobEc.Result);
Assert.Equal(0, _jobEc.PostJobSteps.Count);
}
}
} }
} }

View File

@@ -3,9 +3,9 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Runtime.CompilerServices;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using GitHub.Runner.Worker; using GitHub.Runner.Worker;
using GitHub.Runner.Worker.Container; using GitHub.Runner.Worker.Container;
@@ -937,19 +937,6 @@ namespace GitHub.Runner.Common.Tests.Worker
} }
} }
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void CaptureTelemetryForGitUnsafeRepository()
{
using (Setup())
using (_outputManager)
{
Process("fatal: unsafe repository ('/github/workspace' is owned by someone else)");
Assert.Contains("fatal: unsafe repository ('/github/workspace' is owned by someone else)", _executionContext.Object.StepTelemetry.ErrorMessages);
}
}
private TestHostContext Setup( private TestHostContext Setup(
[CallerMemberName] string name = "", [CallerMemberName] string name = "",
IssueMatchersConfig matchers = null, IssueMatchersConfig matchers = null,
@@ -975,8 +962,6 @@ namespace GitHub.Runner.Common.Tests.Worker
Variables = _variables, Variables = _variables,
WriteDebug = true, WriteDebug = true,
}); });
_executionContext.Setup(x => x.StepTelemetry)
.Returns(new DTWebApi.ActionsStepTelemetry());
_executionContext.Setup(x => x.GetMatchers()) _executionContext.Setup(x => x.GetMatchers())
.Returns(matchers?.Matchers ?? new List<IssueMatcherConfig>()); .Returns(matchers?.Matchers ?? new List<IssueMatcherConfig>());
_executionContext.Setup(x => x.Add(It.IsAny<OnMatcherChanged>())) _executionContext.Setup(x => x.Add(It.IsAny<OnMatcherChanged>()))

View File

@@ -7,9 +7,6 @@ using Xunit;
using GitHub.Runner.Worker; using GitHub.Runner.Worker;
using GitHub.Runner.Worker.Handlers; using GitHub.Runner.Worker.Handlers;
using GitHub.Runner.Worker.Container; using GitHub.Runner.Worker.Container;
using GitHub.DistributedTask.Pipelines.ContextData;
using System.Linq;
using GitHub.DistributedTask.Pipelines;
namespace GitHub.Runner.Common.Tests.Worker namespace GitHub.Runner.Common.Tests.Worker
{ {

View File

@@ -622,40 +622,6 @@ namespace GitHub.Runner.Common.Tests.Worker
_stepContext.SetOutcome("", stepContext.Object.ContextName, (stepContext.Object.Outcome ?? stepContext.Object.Result ?? TaskResult.Succeeded).ToActionResult()); _stepContext.SetOutcome("", stepContext.Object.ContextName, (stepContext.Object.Outcome ?? stepContext.Object.Result ?? TaskResult.Succeeded).ToActionResult());
_stepContext.SetConclusion("", stepContext.Object.ContextName, (stepContext.Object.Result ?? TaskResult.Succeeded).ToActionResult()); _stepContext.SetConclusion("", stepContext.Object.ContextName, (stepContext.Object.Result ?? TaskResult.Succeeded).ToActionResult());
}); });
stepContext.Setup(x => x.UpdateGlobalStepsContext()).Callback(() =>
{
if (!string.IsNullOrEmpty(stepContext.Object.ContextName) && !stepContext.Object.ContextName.StartsWith("__", StringComparison.Ordinal))
{
stepContext.Object.Global.StepsContext.SetOutcome(stepContext.Object.ScopeName, stepContext.Object.ContextName, (stepContext.Object.Outcome ?? stepContext.Object.Result ?? TaskResult.Succeeded).ToActionResult());
stepContext.Object.Global.StepsContext.SetConclusion(stepContext.Object.ScopeName, stepContext.Object.ContextName, (stepContext.Object.Result ?? TaskResult.Succeeded).ToActionResult());
}
});
stepContext.Setup(x => x.ApplyContinueOnError(It.IsAny<TemplateToken>())).Callback((TemplateToken token) =>
{
if (stepContext.Object.Result != TaskResult.Failed)
{
return;
}
var continueOnError = false;
try
{
var templateEvaluator = stepContext.Object.ToPipelineTemplateEvaluator();
continueOnError = templateEvaluator.EvaluateStepContinueOnError(token, stepContext.Object.ExpressionValues, stepContext.Object.ExpressionFunctions);
}
catch (Exception ex)
{
stepContext.Object.Error("The step failed and an error occurred when attempting to determine whether to continue on error.");
stepContext.Object.Error(ex);
}
if (continueOnError)
{
stepContext.Object.Outcome = stepContext.Object.Result;
stepContext.Object.Result = TaskResult.Succeeded;
}
stepContext.Object.UpdateGlobalStepsContext();
});
var trace = hc.GetTrace(); var trace = hc.GetTrace();
stepContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { trace.Info($"[{tag}]{message}"); }); stepContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { trace.Info($"[{tag}]{message}"); });
stepContext.Object.Result = result; stepContext.Object.Result = result;

View File

@@ -128,7 +128,6 @@ function layout ()
chmod +x "${LAYOUT_DIR}/bin/Runner.Worker" chmod +x "${LAYOUT_DIR}/bin/Runner.Worker"
chmod +x "${LAYOUT_DIR}/bin/Runner.PluginHost" chmod +x "${LAYOUT_DIR}/bin/Runner.PluginHost"
chmod +x "${LAYOUT_DIR}/bin/installdependencies.sh" chmod +x "${LAYOUT_DIR}/bin/installdependencies.sh"
chmod +x "${LAYOUT_DIR}/safe_sleep.sh"
fi fi
heading "Setup externals folder for $RUNTIME_ID runner's layout" heading "Setup externals folder for $RUNTIME_ID runner's layout"

View File

@@ -1 +1 @@
2.290.1 2.287.1