mirror of
https://github.com/actions/runner.git
synced 2025-12-12 05:37:01 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
436989949b | ||
|
|
06f7d8a4a3 | ||
|
|
06e1773933 | ||
|
|
844595b1b3 | ||
|
|
98a1935e50 | ||
|
|
4d908df4b2 | ||
|
|
b53b520498 | ||
|
|
e6baf0d275 | ||
|
|
e7ad4652b6 | ||
|
|
b03ca604ff | ||
|
|
ecfc2cc9e9 | ||
|
|
740fb43731 | ||
|
|
f259e5706f | ||
|
|
5d84918ed5 | ||
|
|
881c521005 | ||
|
|
176e7f5208 | ||
|
|
b6d46c148a | ||
|
|
38e33bb8e3 |
8
.editorconfig
Normal file
8
.editorconfig
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# https://editorconfig.org/
|
||||||
|
|
||||||
|
[*]
|
||||||
|
insert_final_newline = true # ensure all files end with a single newline
|
||||||
|
trim_trailing_whitespace = true # attempt to remove trailing whitespace on save
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false # in markdown, "two trailing spaces" is unfortunately meaningful; it means `<br>`
|
||||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
|||||||
devScript: ./dev.sh
|
devScript: ./dev.sh
|
||||||
|
|
||||||
- runtime: win-x64
|
- runtime: win-x64
|
||||||
os: windows-latest
|
os: windows-2019
|
||||||
devScript: ./dev
|
devScript: ./dev
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|||||||
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@@ -72,7 +72,7 @@ jobs:
|
|||||||
devScript: ./dev.sh
|
devScript: ./dev.sh
|
||||||
|
|
||||||
- runtime: win-x64
|
- runtime: win-x64
|
||||||
os: windows-latest
|
os: windows-2019
|
||||||
devScript: ./dev
|
devScript: ./dev
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
@@ -164,6 +164,7 @@ jobs:
|
|||||||
release_name: "v${{ steps.releaseNote.outputs.version }}"
|
release_name: "v${{ steps.releaseNote.outputs.version }}"
|
||||||
body: |
|
body: |
|
||||||
${{ steps.releaseNote.outputs.note }}
|
${{ steps.releaseNote.outputs.note }}
|
||||||
|
prerelease: true
|
||||||
|
|
||||||
# Upload release assets
|
# Upload release assets
|
||||||
- name: Upload Release Asset (win-x64)
|
- name: Upload Release Asset (win-x64)
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ If you are having trouble connecting, try these steps:
|
|||||||
- You may need to configure your TLS settings to use the correct version
|
- You may need to configure your TLS settings to use the correct version
|
||||||
- You should support TLS version 1.2 or later
|
- You should support TLS version 1.2 or later
|
||||||
- You may need to configure your TLS settings to have up to date cipher suites, this may be solved by system updates and patches.
|
- You may need to configure your TLS settings to have up to date cipher suites, this may be solved by system updates and patches.
|
||||||
|
- Most notably, on windows server 2012 make sure [the tls cipher suite update](https://support.microsoft.com/en-us/topic/update-adds-new-tls-cipher-suites-and-changes-cipher-suite-priorities-in-windows-8-1-and-windows-server-2012-r2-8e395e43-c8ef-27d8-b60c-0fc57d526d94) is installed
|
||||||
- Your firewall, proxy or network configuration may be blocking the connection
|
- Your firewall, proxy or network configuration may be blocking the connection
|
||||||
- You will want to reach out to whoever is in charge of your network with these pcap files to further troubleshoot
|
- You will want to reach out to whoever is in charge of your network with these pcap files to further troubleshoot
|
||||||
- If you see a failure later in the handshake:
|
- If you see a failure later in the handshake:
|
||||||
|
|||||||
@@ -1,21 +1,11 @@
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Support the `--ephemeral` flag (#660)
|
|
||||||
- This optional flag will configure the runner to only take one job, and let the service un-configure the runner after that job finishes.
|
|
||||||
- Expect to see more info in the Github API documentation soon. We'll link to those docs directly as they become generally available!
|
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
|
|
||||||
- Fix a bug in `script/delete` wherein a repo with multiple runners would be unable to find the correct runner (#1268) (#1269)
|
- Fixed an issue where container environment variables names or values could escape the docker command (#2108)
|
||||||
- Mitigate a race condition when requesting an OIDC `Id_token` (#1320)
|
|
||||||
- Make client retries more resilient in JobServer (#1316)
|
|
||||||
|
|
||||||
## Misc
|
## Misc
|
||||||
|
|
||||||
- Increase readability of colored console output (#1295) (#1319)
|
|
||||||
- Add more network troubleshooting to the docs (#1325)
|
|
||||||
- Bump [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7 (#1256)
|
|
||||||
|
|
||||||
## 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.
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.282.0
|
2.283.4
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ downloadrunnerversion=_DOWNLOAD_RUNNER_VERSION_
|
|||||||
logfile="_UPDATE_LOG_"
|
logfile="_UPDATE_LOG_"
|
||||||
restartinteractiverunner=_RESTART_INTERACTIVE_RUNNER_
|
restartinteractiverunner=_RESTART_INTERACTIVE_RUNNER_
|
||||||
|
|
||||||
|
telemetryfile="$rootfolder/_diag/.telemetry"
|
||||||
|
|
||||||
# log user who run the script
|
# log user who run the script
|
||||||
date "+[%F %T-%4N] --------whoami--------" >> "$logfile" 2>&1
|
date "+[%F %T-%4N] --------whoami--------" >> "$logfile" 2>&1
|
||||||
whoami >> "$logfile" 2>&1
|
whoami >> "$logfile" 2>&1
|
||||||
@@ -118,40 +120,101 @@ then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# fix upgrade issue with macOS
|
# fix upgrade issue with macOS when running as a service
|
||||||
|
attemptedtargetedfix=0
|
||||||
currentplatform=$(uname | awk '{print tolower($0)}')
|
currentplatform=$(uname | awk '{print tolower($0)}')
|
||||||
if [[ "$currentplatform" == 'darwin' ]]; then
|
if [[ "$currentplatform" == 'darwin' && restartinteractiverunner -eq 0 ]]; then
|
||||||
# need a short-term fix for https://github.com/actions/runner/issues/743
|
# We needed a fix for https://github.com/actions/runner/issues/743
|
||||||
# we will recreate all the ./externals/node12/bin/node of the past 5 versions
|
# We will recreate the ./externals/node12/bin/node of the past runner version that launched the runnerlistener service
|
||||||
# v2.280.3 v2.280.2 v2.280.1 v2.279.0 v2.278.0
|
# Otherwise mac gatekeeper kills the processes we spawn on creation as we are running a process with no backing file
|
||||||
if [[ ! -e "$rootfolder/externals.2.280.3/node12/bin/node" ]]
|
|
||||||
|
# We need the pid for the nodejs loop, get that here, its the parent of the runner C# pid
|
||||||
|
# assumption here is only one process is invoking rootfolder/runsvc.sh
|
||||||
|
procgroup=$(ps x -o pgid,command | grep "$rootfolder/runsvc.sh" | grep -v grep | awk '{print $1}')
|
||||||
|
if [[ $? -eq 0 && -n "$procgroup" ]]
|
||||||
then
|
then
|
||||||
mkdir -p "$rootfolder/externals.2.280.3/node12/bin"
|
# inspect the open file handles to find the node process
|
||||||
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.280.3/node12/bin/node"
|
# we can't actually inspect the process using ps because it uses relative paths and doesn't follow symlinks
|
||||||
|
path=$(lsof -a -g "$procgroup" -F n | grep node12/bin/node | grep externals | tail -1 | cut -c2-)
|
||||||
|
if [[ $? -eq 0 && -n "$path" ]]
|
||||||
|
then
|
||||||
|
# trim the last 5 characters of the path '/node'
|
||||||
|
trimmedpath=$(dirname "$path")
|
||||||
|
if [[ $? -eq 0 && -n "$trimmedpath" ]]
|
||||||
|
then
|
||||||
|
attemptedtargetedfix=1
|
||||||
|
# Create the path if it does not exist
|
||||||
|
if [[ ! -e "$path" ]]
|
||||||
|
then
|
||||||
|
date "+[%F %T-%4N] Creating fallback node at path $path" >> "$logfile" 2>&1
|
||||||
|
mkdir -p "$trimmedpath"
|
||||||
|
cp "$rootfolder/externals/node12/bin/node" "$path"
|
||||||
|
else
|
||||||
|
date "+[%F %T-%4N] Path for fallback node exists, skipping creating $path" >> "$logfile" 2>&1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
date "+[%F %T-%4N] DarwinRunnerUpgrade: Failed to trim runner path. TrimmedPath: $trimmedpath, path: $path, pgid: $procgroup, root: $rootfolder" >> "$logfile" 2>&1
|
||||||
|
date "+[%F %T-%4N] DarwinRunnerUpgrade: Failed to trim runner path. TrimmedPath: $trimmedpath, path: $path, pgid: $procgroup, root: $rootfolder" >> "$telemetryfile" 2>&1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
date "+[%F %T-%4N] DarwinRunnerUpgrade: Failed to find runner path. Path: $path, pgid: $procgroup, root: $rootfolder" >> "$logfile" 2>&1
|
||||||
|
date "+[%F %T-%4N] DarwinRunnerUpgrade: Failed to find runner path. Path: $path, pgid: $procgroup, root: $rootfolder" >> "$telemetryfile" 2>&1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
date "+[%F %T-%4N] DarwinRunnerUpgrade: Failed to find runner pgid. pgid: $procgroup, root: $rootfolder" >> "$logfile" 2>&1
|
||||||
|
date "+[%F %T-%4N] DarwinRunnerUpgrade: Failed to find runner pgid. pgid: $procgroup, root: $rootfolder" >> "$telemetryfile" 2>&1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -e "$rootfolder/externals.2.280.2/node12/bin/node" ]]
|
if [ $attemptedtargetedfix -eq 0 ]
|
||||||
then
|
then
|
||||||
mkdir -p "$rootfolder/externals.2.280.2/node12/bin"
|
|
||||||
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.280.2/node12/bin/node"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! -e "$rootfolder/externals.2.280.1/node12/bin/node" ]]
|
date "+[%F %T-%4N] DarwinRunnerUpgrade: Defaulting to old macOS service fix" >> "$logfile" 2>&1
|
||||||
then
|
date "+[%F %T-%4N] DarwinRunnerUpgrade: Defaulting to old macOS service fix" >> "$telemetryfile" 2>&1
|
||||||
mkdir -p "$rootfolder/externals.2.280.1/node12/bin"
|
if [[ ! -e "$rootfolder/externals.2.280.3/node12/bin/node" ]]
|
||||||
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.280.1/node12/bin/node"
|
then
|
||||||
fi
|
mkdir -p "$rootfolder/externals.2.280.3/node12/bin"
|
||||||
|
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.280.3/node12/bin/node"
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ ! -e "$rootfolder/externals.2.279.0/node12/bin/node" ]]
|
if [[ ! -e "$rootfolder/externals.2.280.2/node12/bin/node" ]]
|
||||||
then
|
then
|
||||||
mkdir -p "$rootfolder/externals.2.279.0/node12/bin"
|
mkdir -p "$rootfolder/externals.2.280.2/node12/bin"
|
||||||
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.279.0/node12/bin/node"
|
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.280.2/node12/bin/node"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -e "$rootfolder/externals.2.278.0/node12/bin/node" ]]
|
if [[ ! -e "$rootfolder/externals.2.280.1/node12/bin/node" ]]
|
||||||
then
|
then
|
||||||
mkdir -p "$rootfolder/externals.2.278.0/node12/bin"
|
mkdir -p "$rootfolder/externals.2.280.1/node12/bin"
|
||||||
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.278.0/node12/bin/node"
|
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.280.1/node12/bin/node"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# GHES 3.2
|
||||||
|
if [[ ! -e "$rootfolder/externals.2.279.0/node12/bin/node" ]]
|
||||||
|
then
|
||||||
|
mkdir -p "$rootfolder/externals.2.279.0/node12/bin"
|
||||||
|
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.279.0/node12/bin/node"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# GHES 3.1.2 or later
|
||||||
|
if [[ ! -e "$rootfolder/externals.2.278.0/node12/bin/node" ]]
|
||||||
|
then
|
||||||
|
mkdir -p "$rootfolder/externals.2.278.0/node12/bin"
|
||||||
|
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.278.0/node12/bin/node"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# GHES 3.1.0
|
||||||
|
if [[ ! -e "$rootfolder/externals.2.276.1/node12/bin/node" ]]
|
||||||
|
then
|
||||||
|
mkdir -p "$rootfolder/externals.2.276.1/node12/bin"
|
||||||
|
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.276.1/node12/bin/node"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# GHES 3.0
|
||||||
|
if [[ ! -e "$rootfolder/externals.2.273.5/node12/bin/node" ]]
|
||||||
|
then
|
||||||
|
mkdir -p "$rootfolder/externals.2.273.5/node12/bin"
|
||||||
|
cp "$rootfolder/externals/node12/bin/node" "$rootfolder/externals.2.273.5/node12/bin/node"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,21 @@ else
|
|||||||
else
|
else
|
||||||
sleep 5
|
sleep 5
|
||||||
fi
|
fi
|
||||||
|
elif [[ $returnCode == 4 ]]; then
|
||||||
|
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
|
||||||
|
"$DIR"/bin/Runner.Listener run $*
|
||||||
else
|
else
|
||||||
exit $returnCode
|
exit $returnCode
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ namespace GitHub.Runner.Common
|
|||||||
Certificates,
|
Certificates,
|
||||||
Options,
|
Options,
|
||||||
SetupInfo,
|
SetupInfo,
|
||||||
|
Telemetry
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Constants
|
public static class Constants
|
||||||
@@ -128,7 +129,7 @@ namespace GitHub.Runner.Common
|
|||||||
public static readonly string Ephemeral = "ephemeral";
|
public static readonly string Ephemeral = "ephemeral";
|
||||||
public static readonly string Help = "help";
|
public static readonly string Help = "help";
|
||||||
public static readonly string Replace = "replace";
|
public static readonly string Replace = "replace";
|
||||||
public static readonly string Once = "once"; // TODO: Remove in 10/2021
|
public static readonly string Once = "once"; // Keep this around since customers still relies on it
|
||||||
public static readonly string RunAsService = "runasservice";
|
public static readonly string RunAsService = "runasservice";
|
||||||
public static readonly string Unattended = "unattended";
|
public static readonly string Unattended = "unattended";
|
||||||
public static readonly string Version = "version";
|
public static readonly string Version = "version";
|
||||||
@@ -154,6 +155,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/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 class RunnerEvent
|
public static class RunnerEvent
|
||||||
@@ -213,6 +215,7 @@ namespace GitHub.Runner.Common
|
|||||||
// Keep alphabetical
|
// Keep alphabetical
|
||||||
//
|
//
|
||||||
public static readonly string AllowUnsupportedCommands = "ACTIONS_ALLOW_UNSECURE_COMMANDS";
|
public static readonly string AllowUnsupportedCommands = "ACTIONS_ALLOW_UNSECURE_COMMANDS";
|
||||||
|
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";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -343,6 +343,12 @@ namespace GitHub.Runner.Common
|
|||||||
".setup_info");
|
".setup_info");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case WellKnownConfigFile.Telemetry:
|
||||||
|
path = Path.Combine(
|
||||||
|
GetDirectory(WellKnownDirectory.Diag),
|
||||||
|
".telemetry");
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new NotSupportedException($"Unexpected well known config file: '{configFile}'");
|
throw new NotSupportedException($"Unexpected well known config file: '{configFile}'");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,19 +2,16 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.Runner.Sdk;
|
|
||||||
using GitHub.Services.WebApi;
|
using GitHub.Services.WebApi;
|
||||||
using GitHub.Services.Common;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Common
|
namespace GitHub.Runner.Common
|
||||||
{
|
{
|
||||||
[ServiceLocator(Default = typeof(JobServer))]
|
[ServiceLocator(Default = typeof(JobServer))]
|
||||||
public interface IJobServer : IRunnerService
|
public interface IJobServer : IRunnerService
|
||||||
{
|
{
|
||||||
Task ConnectAsync(Uri jobServerUrl, VssCredentials jobServerCredential, DelegatingHandler[] delegatingHandler = null);
|
Task ConnectAsync(VssConnection jobConnection);
|
||||||
|
|
||||||
// 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);
|
||||||
@@ -35,21 +32,20 @@ namespace GitHub.Runner.Common
|
|||||||
private VssConnection _connection;
|
private VssConnection _connection;
|
||||||
private TaskHttpClient _taskClient;
|
private TaskHttpClient _taskClient;
|
||||||
|
|
||||||
public async Task ConnectAsync(Uri jobServerUrl, VssCredentials jobServerCredential, DelegatingHandler[] delegatingHandler = null)
|
public async Task ConnectAsync(VssConnection jobConnection)
|
||||||
{
|
{
|
||||||
Trace.Info($"Establishing connection for JobServer");
|
_connection = jobConnection;
|
||||||
int attemptCount = 5;
|
int attemptCount = 5;
|
||||||
|
while (!_connection.HasAuthenticated && attemptCount-- > 0)
|
||||||
while (attemptCount-- > 0)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await RefreshConnectionAsync(jobServerUrl, jobServerCredential, delegatingHandler);
|
await _connection.ConnectAsync();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (attemptCount > 0)
|
catch (Exception ex) when (attemptCount > 0)
|
||||||
{
|
{
|
||||||
Trace.Info($"Catch exception during connect. {attemptCount} attempts left.");
|
Trace.Info($"Catch exception during connect. {attemptCount} attemp left.");
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,15 +53,6 @@ namespace GitHub.Runner.Common
|
|||||||
}
|
}
|
||||||
|
|
||||||
_taskClient = _connection.GetClient<TaskHttpClient>();
|
_taskClient = _connection.GetClient<TaskHttpClient>();
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RefreshConnectionAsync(Uri jobServerUrl, VssCredentials jobServerCredential, DelegatingHandler[] delegatingHandler)
|
|
||||||
{
|
|
||||||
Trace.Info($"Refresh JobServer VssConnection to get on a different AFD node.");
|
|
||||||
_hasConnection = false;
|
|
||||||
_connection?.Dispose();
|
|
||||||
_connection = VssUtil.CreateConnection(jobServerUrl, jobServerCredential, delegatingHandler);
|
|
||||||
await _connection.ConnectAsync();
|
|
||||||
_hasConnection = true;
|
_hasConnection = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,10 @@ namespace GitHub.Runner.Common
|
|||||||
// Configuration
|
// Configuration
|
||||||
Task<TaskAgent> AddAgentAsync(Int32 agentPoolId, TaskAgent agent);
|
Task<TaskAgent> AddAgentAsync(Int32 agentPoolId, TaskAgent agent);
|
||||||
Task DeleteAgentAsync(int agentPoolId, int agentId);
|
Task DeleteAgentAsync(int agentPoolId, int agentId);
|
||||||
|
Task DeleteAgentAsync(int agentId);
|
||||||
Task<List<TaskAgentPool>> GetAgentPoolsAsync(string agentPoolName = null, TaskAgentPoolType poolType = TaskAgentPoolType.Automation);
|
Task<List<TaskAgentPool>> GetAgentPoolsAsync(string agentPoolName = null, TaskAgentPoolType poolType = TaskAgentPoolType.Automation);
|
||||||
Task<List<TaskAgent>> GetAgentsAsync(int agentPoolId, string agentName = null);
|
Task<List<TaskAgent>> GetAgentsAsync(int agentPoolId, string agentName = null);
|
||||||
|
Task<List<TaskAgent>> GetAgentsAsync(string agentName);
|
||||||
Task<TaskAgent> ReplaceAgentAsync(int agentPoolId, TaskAgent agent);
|
Task<TaskAgent> ReplaceAgentAsync(int agentPoolId, TaskAgent agent);
|
||||||
|
|
||||||
// messagequeue
|
// messagequeue
|
||||||
@@ -252,6 +254,11 @@ namespace GitHub.Runner.Common
|
|||||||
return _genericTaskAgentClient.GetAgentsAsync(agentPoolId, agentName, false);
|
return _genericTaskAgentClient.GetAgentsAsync(agentPoolId, agentName, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<List<TaskAgent>> GetAgentsAsync(string agentName)
|
||||||
|
{
|
||||||
|
return GetAgentsAsync(0, agentName); // search in all all agentPools
|
||||||
|
}
|
||||||
|
|
||||||
public Task<TaskAgent> ReplaceAgentAsync(int agentPoolId, TaskAgent agent)
|
public Task<TaskAgent> ReplaceAgentAsync(int agentPoolId, TaskAgent agent)
|
||||||
{
|
{
|
||||||
CheckConnection(RunnerConnectionType.Generic);
|
CheckConnection(RunnerConnectionType.Generic);
|
||||||
@@ -264,6 +271,11 @@ namespace GitHub.Runner.Common
|
|||||||
return _genericTaskAgentClient.DeleteAgentAsync(agentPoolId, agentId);
|
return _genericTaskAgentClient.DeleteAgentAsync(agentPoolId, agentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task DeleteAgentAsync(int agentId)
|
||||||
|
{
|
||||||
|
return DeleteAgentAsync(0, agentId); // agentPool is ignored server side
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------
|
//-----------------------------------------------------------------
|
||||||
// MessageQueue
|
// MessageQueue
|
||||||
//-----------------------------------------------------------------
|
//-----------------------------------------------------------------
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ namespace GitHub.Runner.Listener
|
|||||||
Constants.Runner.CommandLine.Flags.Commit,
|
Constants.Runner.CommandLine.Flags.Commit,
|
||||||
Constants.Runner.CommandLine.Flags.Ephemeral,
|
Constants.Runner.CommandLine.Flags.Ephemeral,
|
||||||
Constants.Runner.CommandLine.Flags.Help,
|
Constants.Runner.CommandLine.Flags.Help,
|
||||||
|
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,
|
||||||
@@ -68,7 +69,7 @@ namespace GitHub.Runner.Listener
|
|||||||
public bool Version => TestFlag(Constants.Runner.CommandLine.Flags.Version);
|
public bool Version => TestFlag(Constants.Runner.CommandLine.Flags.Version);
|
||||||
public bool Ephemeral => TestFlag(Constants.Runner.CommandLine.Flags.Ephemeral);
|
public bool Ephemeral => TestFlag(Constants.Runner.CommandLine.Flags.Ephemeral);
|
||||||
|
|
||||||
// TODO: Remove in 10/2021
|
// Keep this around since customers still relies on it
|
||||||
public bool RunOnce => TestFlag(Constants.Runner.CommandLine.Flags.Once);
|
public bool RunOnce => TestFlag(Constants.Runner.CommandLine.Flags.Once);
|
||||||
|
|
||||||
// Constructor.
|
// Constructor.
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
bool IsConfigured();
|
bool IsConfigured();
|
||||||
Task ConfigureAsync(CommandSettings command);
|
Task ConfigureAsync(CommandSettings command);
|
||||||
Task UnconfigureAsync(CommandSettings command);
|
Task UnconfigureAsync(CommandSettings command);
|
||||||
|
void DeleteLocalRunnerConfig();
|
||||||
RunnerSettings LoadSettings();
|
RunnerSettings LoadSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,6 +330,38 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete .runner and .credentials files
|
||||||
|
public void DeleteLocalRunnerConfig()
|
||||||
|
{
|
||||||
|
bool isConfigured = _store.IsConfigured();
|
||||||
|
bool hasCredentials = _store.HasCredentials();
|
||||||
|
//delete credential config files
|
||||||
|
var currentAction = "Removing .credentials";
|
||||||
|
if (hasCredentials)
|
||||||
|
{
|
||||||
|
_store.DeleteCredential();
|
||||||
|
var keyManager = HostContext.GetService<IRSAKeyManager>();
|
||||||
|
keyManager.DeleteKey();
|
||||||
|
_term.WriteSuccessMessage("Removed .credentials");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_term.WriteLine("Does not exist. Skipping " + currentAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
//delete settings config file
|
||||||
|
currentAction = "Removing .runner";
|
||||||
|
if (isConfigured)
|
||||||
|
{
|
||||||
|
_store.DeleteSettings();
|
||||||
|
_term.WriteSuccessMessage("Removed .runner");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_term.WriteLine("Does not exist. Skipping " + currentAction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task UnconfigureAsync(CommandSettings command)
|
public async Task UnconfigureAsync(CommandSettings command)
|
||||||
{
|
{
|
||||||
string currentAction = string.Empty;
|
string currentAction = string.Empty;
|
||||||
@@ -382,7 +415,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
// Determine the service deployment type based on connection data. (Hosted/OnPremises)
|
// Determine the service deployment type based on connection data. (Hosted/OnPremises)
|
||||||
await _runnerServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
await _runnerServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
||||||
|
|
||||||
var agents = await _runnerServer.GetAgentsAsync(settings.PoolId, settings.AgentName);
|
var agents = await _runnerServer.GetAgentsAsync(settings.AgentName);
|
||||||
Trace.Verbose("Returns {0} agents", agents.Count);
|
Trace.Verbose("Returns {0} agents", agents.Count);
|
||||||
TaskAgent agent = agents.FirstOrDefault();
|
TaskAgent agent = agents.FirstOrDefault();
|
||||||
if (agent == null)
|
if (agent == null)
|
||||||
@@ -391,7 +424,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await _runnerServer.DeleteAgentAsync(settings.PoolId, settings.AgentId);
|
await _runnerServer.DeleteAgentAsync(settings.AgentId);
|
||||||
|
|
||||||
_term.WriteLine();
|
_term.WriteLine();
|
||||||
_term.WriteSuccessMessage("Runner removed successfully");
|
_term.WriteSuccessMessage("Runner removed successfully");
|
||||||
@@ -402,31 +435,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
_term.WriteLine("Cannot connect to server, because config files are missing. Skipping removing runner from the server.");
|
_term.WriteLine("Cannot connect to server, because config files are missing. Skipping removing runner from the server.");
|
||||||
}
|
}
|
||||||
|
|
||||||
//delete credential config files
|
DeleteLocalRunnerConfig();
|
||||||
currentAction = "Removing .credentials";
|
|
||||||
if (hasCredentials)
|
|
||||||
{
|
|
||||||
_store.DeleteCredential();
|
|
||||||
var keyManager = HostContext.GetService<IRSAKeyManager>();
|
|
||||||
keyManager.DeleteKey();
|
|
||||||
_term.WriteSuccessMessage("Removed .credentials");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_term.WriteLine("Does not exist. Skipping " + currentAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
//delete settings config file
|
|
||||||
currentAction = "Removing .runner";
|
|
||||||
if (isConfigured)
|
|
||||||
{
|
|
||||||
_store.DeleteSettings();
|
|
||||||
_term.WriteSuccessMessage("Removed .runner");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_term.WriteLine("Does not exist. Skipping " + currentAction);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,7 +36,10 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
private readonly Lazy<Dictionary<long, TaskResult>> _localRunJobResult = new Lazy<Dictionary<long, TaskResult>>();
|
private readonly Lazy<Dictionary<long, TaskResult>> _localRunJobResult = new Lazy<Dictionary<long, TaskResult>>();
|
||||||
private int _poolId;
|
private int _poolId;
|
||||||
RunnerSettings _runnerSetting;
|
|
||||||
|
IConfigurationStore _configurationStore;
|
||||||
|
|
||||||
|
RunnerSettings _runnerSettings;
|
||||||
private static readonly string _workerProcessName = $"Runner.Worker{IOUtil.ExeExtension}";
|
private static readonly string _workerProcessName = $"Runner.Worker{IOUtil.ExeExtension}";
|
||||||
|
|
||||||
// this is not thread-safe
|
// this is not thread-safe
|
||||||
@@ -54,9 +57,9 @@ namespace GitHub.Runner.Listener
|
|||||||
base.Initialize(hostContext);
|
base.Initialize(hostContext);
|
||||||
|
|
||||||
// get pool id from config
|
// get pool id from config
|
||||||
var configurationStore = hostContext.GetService<IConfigurationStore>();
|
_configurationStore = hostContext.GetService<IConfigurationStore>();
|
||||||
_runnerSetting = configurationStore.GetSettings();
|
_runnerSettings = _configurationStore.GetSettings();
|
||||||
_poolId = _runnerSetting.PoolId;
|
_poolId = _runnerSettings.PoolId;
|
||||||
|
|
||||||
int channelTimeoutSeconds;
|
int channelTimeoutSeconds;
|
||||||
if (!int.TryParse(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_CHANNEL_TIMEOUT") ?? string.Empty, out channelTimeoutSeconds))
|
if (!int.TryParse(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_CHANNEL_TIMEOUT") ?? string.Empty, out channelTimeoutSeconds))
|
||||||
@@ -510,8 +513,9 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
var jobServer = HostContext.GetService<IJobServer>();
|
var jobServer = HostContext.GetService<IJobServer>();
|
||||||
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
|
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
|
||||||
|
VssConnection jobConnection = VssUtil.CreateConnection(systemConnection.Url, jobServerCredential);
|
||||||
|
await jobServer.ConnectAsync(jobConnection);
|
||||||
|
|
||||||
await jobServer.ConnectAsync(systemConnection.Url, jobServerCredential);
|
|
||||||
await LogWorkerProcessUnhandledException(jobServer, message, detailInfo);
|
await LogWorkerProcessUnhandledException(jobServer, message, detailInfo);
|
||||||
|
|
||||||
// Go ahead to finish the job with result 'Failed' if the STDERR from worker is System.IO.IOException, since it typically means we are running out of disk space.
|
// Go ahead to finish the job with result 'Failed' if the STDERR from worker is System.IO.IOException, since it typically means we are running out of disk space.
|
||||||
@@ -660,13 +664,15 @@ namespace GitHub.Runner.Listener
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
request = await runnerServer.RenewAgentRequestAsync(poolId, requestId, lockToken, orchestrationId, token);
|
request = await runnerServer.RenewAgentRequestAsync(poolId, requestId, lockToken, orchestrationId, token);
|
||||||
|
|
||||||
Trace.Info($"Successfully renew job request {requestId}, job is valid till {request.LockedUntil.Value}");
|
Trace.Info($"Successfully renew job request {requestId}, job is valid till {request.LockedUntil.Value}");
|
||||||
|
|
||||||
if (!firstJobRequestRenewed.Task.IsCompleted)
|
if (!firstJobRequestRenewed.Task.IsCompleted)
|
||||||
{
|
{
|
||||||
// fire first renew succeed event.
|
// fire first renew succeed event.
|
||||||
firstJobRequestRenewed.TrySetResult(0);
|
firstJobRequestRenewed.TrySetResult(0);
|
||||||
|
|
||||||
|
// Update settings if the runner name has been changed server-side
|
||||||
|
UpdateAgentNameIfNeeded(request.ReservedAgent?.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (encounteringError > 0)
|
if (encounteringError > 0)
|
||||||
@@ -766,6 +772,27 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateAgentNameIfNeeded(string agentName)
|
||||||
|
{
|
||||||
|
var isNewAgentName = !string.Equals(_runnerSettings.AgentName, agentName, StringComparison.Ordinal);
|
||||||
|
if (!isNewAgentName || string.IsNullOrEmpty(agentName))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_runnerSettings.AgentName = agentName;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_configurationStore.SaveSettings(_runnerSettings);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Trace.Error("Cannot update the settings file:");
|
||||||
|
Trace.Error(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Best effort upload any logs for this job.
|
// Best effort upload any logs for this job.
|
||||||
private async Task TryUploadUnfinishedLogs(Pipelines.AgentJobRequestMessage message)
|
private async Task TryUploadUnfinishedLogs(Pipelines.AgentJobRequestMessage message)
|
||||||
{
|
{
|
||||||
@@ -790,8 +817,9 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
var jobServer = HostContext.GetService<IJobServer>();
|
var jobServer = HostContext.GetService<IJobServer>();
|
||||||
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
|
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
|
||||||
|
VssConnection jobConnection = VssUtil.CreateConnection(systemConnection.Url, jobServerCredential);
|
||||||
|
|
||||||
await jobServer.ConnectAsync(systemConnection.Url, jobServerCredential);
|
await jobServer.ConnectAsync(jobConnection);
|
||||||
|
|
||||||
var timeline = await jobServer.GetTimelineAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, CancellationToken.None);
|
var timeline = await jobServer.GetTimelineAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, CancellationToken.None);
|
||||||
|
|
||||||
|
|||||||
@@ -233,8 +233,14 @@ namespace GitHub.Runner.Listener
|
|||||||
Trace.Info($"Set runner startup type - {startType}");
|
Trace.Info($"Set runner startup type - {startType}");
|
||||||
HostContext.StartupType = startType;
|
HostContext.StartupType = startType;
|
||||||
|
|
||||||
|
if (command.RunOnce)
|
||||||
|
{
|
||||||
|
_term.WriteLine("Warning: '--once' is going to be deprecated in the future, please consider using '--ephemeral' during runner registration.", ConsoleColor.Yellow);
|
||||||
|
_term.WriteLine("https://docs.github.com/en/actions/hosting-your-own-runners/autoscaling-with-self-hosted-runners#using-ephemeral-runners-for-autoscaling", ConsoleColor.Yellow);
|
||||||
|
}
|
||||||
|
|
||||||
// Run the runner interactively or as service
|
// Run the runner interactively or as service
|
||||||
return await RunAsync(settings, command.RunOnce || settings.Ephemeral); // TODO: Remove RunOnce later.
|
return await RunAsync(settings, command.RunOnce || settings.Ephemeral);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -310,6 +316,9 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
IJobDispatcher jobDispatcher = null;
|
IJobDispatcher jobDispatcher = null;
|
||||||
CancellationTokenSource messageQueueLoopTokenSource = CancellationTokenSource.CreateLinkedTokenSource(HostContext.RunnerShutdownToken);
|
CancellationTokenSource messageQueueLoopTokenSource = CancellationTokenSource.CreateLinkedTokenSource(HostContext.RunnerShutdownToken);
|
||||||
|
|
||||||
|
// Should we try to cleanup ephemeral runners
|
||||||
|
bool runOnceJobCompleted = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var notification = HostContext.GetService<IJobNotification>();
|
var notification = HostContext.GetService<IJobNotification>();
|
||||||
@@ -371,6 +380,7 @@ namespace GitHub.Runner.Listener
|
|||||||
Task completeTask = await Task.WhenAny(getNextMessage, jobDispatcher.RunOnceJobCompleted.Task);
|
Task completeTask = await Task.WhenAny(getNextMessage, jobDispatcher.RunOnceJobCompleted.Task);
|
||||||
if (completeTask == jobDispatcher.RunOnceJobCompleted.Task)
|
if (completeTask == jobDispatcher.RunOnceJobCompleted.Task)
|
||||||
{
|
{
|
||||||
|
runOnceJobCompleted = true;
|
||||||
Trace.Info("Job has finished at backend, the runner will exit since it is running under onetime use mode.");
|
Trace.Info("Job has finished at backend, the runner will exit since it is running under onetime use mode.");
|
||||||
Trace.Info("Stop message queue looping.");
|
Trace.Info("Stop message queue looping.");
|
||||||
messageQueueLoopTokenSource.Cancel();
|
messageQueueLoopTokenSource.Cancel();
|
||||||
@@ -478,6 +488,12 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
|
|
||||||
messageQueueLoopTokenSource.Dispose();
|
messageQueueLoopTokenSource.Dispose();
|
||||||
|
|
||||||
|
if (settings.Ephemeral && runOnceJobCompleted)
|
||||||
|
{
|
||||||
|
var configManager = HostContext.GetService<IConfigurationManager>();
|
||||||
|
configManager.DeleteLocalRunnerConfig();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (TaskAgentAccessTokenExpiredException)
|
catch (TaskAgentAccessTokenExpiredException)
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
using GitHub.DistributedTask.Pipelines;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Common.Util;
|
|
||||||
using GitHub.Runner.Worker.Container;
|
using GitHub.Runner.Worker.Container;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -110,11 +108,18 @@ namespace GitHub.Runner.Worker
|
|||||||
// Stop command
|
// Stop command
|
||||||
if (string.Equals(actionCommand.Command, _stopCommand, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(actionCommand.Command, _stopCommand, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
context.Output(input);
|
ValidateStopToken(context, actionCommand.Data);
|
||||||
context.Debug("Paused processing commands until '##[{actionCommand.Data}]' is received");
|
|
||||||
_stopToken = actionCommand.Data;
|
_stopToken = actionCommand.Data;
|
||||||
_stopProcessCommand = true;
|
_stopProcessCommand = true;
|
||||||
_registeredCommands.Add(_stopToken);
|
_registeredCommands.Add(_stopToken);
|
||||||
|
if (_stopToken.Length > 6)
|
||||||
|
{
|
||||||
|
HostContext.SecretMasker.AddValue(_stopToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Output(input);
|
||||||
|
context.Debug("Paused processing commands until the token you called ::stopCommands:: with is received");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Found command
|
// Found command
|
||||||
@@ -148,7 +153,42 @@ namespace GitHub.Runner.Worker
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static bool EnhancedAnnotationsEnabled(IExecutionContext context) {
|
private void ValidateStopToken(IExecutionContext context, string stopToken)
|
||||||
|
{
|
||||||
|
#if OS_WINDOWS
|
||||||
|
var envContext = context.ExpressionValues["env"] as DictionaryContextData;
|
||||||
|
#else
|
||||||
|
var envContext = context.ExpressionValues["env"] as CaseSensitiveDictionaryContextData;
|
||||||
|
#endif
|
||||||
|
var allowUnsecureStopCommandTokens = false;
|
||||||
|
allowUnsecureStopCommandTokens = StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable(Constants.Variables.Actions.AllowUnsupportedStopCommandTokens));
|
||||||
|
if (!allowUnsecureStopCommandTokens && envContext.ContainsKey(Constants.Variables.Actions.AllowUnsupportedStopCommandTokens))
|
||||||
|
{
|
||||||
|
allowUnsecureStopCommandTokens = StringUtil.ConvertToBoolean(envContext[Constants.Variables.Actions.AllowUnsupportedStopCommandTokens].ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isTokenInvalid = _registeredCommands.Contains(stopToken)
|
||||||
|
|| string.IsNullOrEmpty(stopToken)
|
||||||
|
|| string.Equals(stopToken, "pause-logging", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if (isTokenInvalid)
|
||||||
|
{
|
||||||
|
var telemetry = new JobTelemetry
|
||||||
|
{
|
||||||
|
Message = $"Invoked ::stopCommand:: with token: [{stopToken}]",
|
||||||
|
Type = JobTelemetryType.ActionCommand
|
||||||
|
};
|
||||||
|
context.JobTelemetry.Add(telemetry);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTokenInvalid && !allowUnsecureStopCommandTokens)
|
||||||
|
{
|
||||||
|
throw new Exception(Constants.Runner.UnsupportedStopCommandTokenDisabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool EnhancedAnnotationsEnabled(IExecutionContext context)
|
||||||
|
{
|
||||||
return context.Global.Variables.GetBoolean("DistributedTask.EnhancedAnnotations") ?? false;
|
return context.Global.Variables.GetBoolean("DistributedTask.EnhancedAnnotations") ?? false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -633,7 +633,12 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
catch (Exception ex) when (!executionContext.CancellationToken.IsCancellationRequested) // Do not retry if the run is canceled.
|
catch (Exception ex) when (!executionContext.CancellationToken.IsCancellationRequested) // Do not retry if the run is canceled.
|
||||||
{
|
{
|
||||||
if (attempt < 3)
|
// UnresolvableActionDownloadInfoException is a 422 client error, don't retry
|
||||||
|
// Some possible cases are:
|
||||||
|
// * Repo is rate limited
|
||||||
|
// * Repo or tag doesn't exist, or isn't public
|
||||||
|
// * Policy validation failed
|
||||||
|
if (attempt < 3 && !(ex is WebApi.UnresolvableActionDownloadInfoException))
|
||||||
{
|
{
|
||||||
executionContext.Output($"Failed to resolve action download info. Error: {ex.Message}");
|
executionContext.Output($"Failed to resolve action download info. Error: {ex.Message}");
|
||||||
executionContext.Debug(ex.ToString());
|
executionContext.Debug(ex.ToString());
|
||||||
@@ -649,6 +654,7 @@ namespace GitHub.Runner.Worker
|
|||||||
// Some possible cases are:
|
// Some possible cases are:
|
||||||
// * Repo is rate limited
|
// * Repo is rate limited
|
||||||
// * Repo or tag doesn't exist, or isn't public
|
// * Repo or tag doesn't exist, or isn't public
|
||||||
|
// * Policy validation failed
|
||||||
if (ex is WebApi.UnresolvableActionDownloadInfoException)
|
if (ex is WebApi.UnresolvableActionDownloadInfoException)
|
||||||
{
|
{
|
||||||
throw;
|
throw;
|
||||||
|
|||||||
@@ -131,11 +131,11 @@ namespace GitHub.Runner.Worker.Container
|
|||||||
{
|
{
|
||||||
if (String.IsNullOrEmpty(env.Value))
|
if (String.IsNullOrEmpty(env.Value))
|
||||||
{
|
{
|
||||||
dockerOptions.Add($"-e \"{env.Key}\"");
|
dockerOptions.Add(DockerUtil.CreateEscapedOption("-e", env.Key));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dockerOptions.Add($"-e \"{env.Key}={env.Value.Replace("\"", "\\\"")}\"");
|
dockerOptions.Add(DockerUtil.CreateEscapedOption("-e", env.Key, env.Value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ namespace GitHub.Runner.Worker.Container
|
|||||||
{
|
{
|
||||||
// e.g. -e MY_SECRET maps the value into the exec'ed process without exposing
|
// e.g. -e MY_SECRET maps the value into the exec'ed process without exposing
|
||||||
// the value directly in the command
|
// the value directly in the command
|
||||||
dockerOptions.Add($"-e {env.Key}");
|
dockerOptions.Add(DockerUtil.CreateEscapedOption("-e", env.Key));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watermark for GitHub Action environment
|
// Watermark for GitHub Action environment
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ namespace GitHub.Runner.Worker.Container
|
|||||||
{
|
{
|
||||||
public class DockerUtil
|
public class DockerUtil
|
||||||
{
|
{
|
||||||
|
private static readonly Regex QuoteEscape = new Regex(@"(\\*)" + "\"", RegexOptions.Compiled);
|
||||||
|
private static readonly Regex EndOfStringEscape = new Regex(@"(\\+)$", RegexOptions.Compiled);
|
||||||
|
|
||||||
public static List<PortMapping> ParseDockerPort(IList<string> portMappingLines)
|
public static List<PortMapping> ParseDockerPort(IList<string> portMappingLines)
|
||||||
{
|
{
|
||||||
const string targetPort = "targetPort";
|
const string targetPort = "targetPort";
|
||||||
@@ -17,7 +20,7 @@ namespace GitHub.Runner.Worker.Container
|
|||||||
string pattern = $"^(?<{targetPort}>\\d+)/(?<{proto}>\\w+) -> (?<{host}>.+):(?<{hostPort}>\\d+)$";
|
string pattern = $"^(?<{targetPort}>\\d+)/(?<{proto}>\\w+) -> (?<{host}>.+):(?<{hostPort}>\\d+)$";
|
||||||
|
|
||||||
List<PortMapping> portMappings = new List<PortMapping>();
|
List<PortMapping> portMappings = new List<PortMapping>();
|
||||||
foreach(var line in portMappingLines)
|
foreach (var line in portMappingLines)
|
||||||
{
|
{
|
||||||
Match m = Regex.Match(line, pattern, RegexOptions.None, TimeSpan.FromSeconds(1));
|
Match m = Regex.Match(line, pattern, RegexOptions.None, TimeSpan.FromSeconds(1));
|
||||||
if (m.Success)
|
if (m.Success)
|
||||||
@@ -61,5 +64,44 @@ namespace GitHub.Runner.Worker.Container
|
|||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string CreateEscapedOption(string flag, string key)
|
||||||
|
{
|
||||||
|
if (String.IsNullOrEmpty(key))
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return $"{flag} {EscapeString(key)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string CreateEscapedOption(string flag, string key, string value)
|
||||||
|
{
|
||||||
|
if (String.IsNullOrEmpty(key))
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
var escapedString = EscapeString($"{key}={value}");
|
||||||
|
return $"{flag} {escapedString}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string EscapeString(string value)
|
||||||
|
{
|
||||||
|
if (String.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
// Dotnet escaping rules are weird here, we can only escape \ if it precedes a "
|
||||||
|
// If a double quotation mark follows two or an even number of backslashes, each proceeding backslash pair is replaced with one backslash and the double quotation mark is removed.
|
||||||
|
// If a double quotation mark follows an odd number of backslashes, including just one, each preceding pair is replaced with one backslash and the remaining backslash is removed; however, in this case the double quotation mark is not removed.
|
||||||
|
// https://docs.microsoft.com/en-us/dotnet/api/system.environment.getcommandlineargs?redirectedfrom=MSDN&view=net-6.0#remarks
|
||||||
|
|
||||||
|
// First, find any \ followed by a " and double the number of \ + 1.
|
||||||
|
value = QuoteEscape.Replace(value, @"$1$1\" + "\"");
|
||||||
|
// Next, what if it ends in `\`, it would escape the end quote. So, we need to detect that at the end of the string and perform the same escape
|
||||||
|
// Luckily, we can just use the $ character with detects the end of string in regex
|
||||||
|
value = EndOfStringEscape.Replace(value, @"$1$1");
|
||||||
|
// Finally, wrap it in quotes
|
||||||
|
return $"\"{value}\"";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ namespace GitHub.Runner.Worker
|
|||||||
Dictionary<string, VariableValue> JobOutputs { get; }
|
Dictionary<string, VariableValue> JobOutputs { get; }
|
||||||
ActionsEnvironmentReference ActionsEnvironment { get; }
|
ActionsEnvironmentReference ActionsEnvironment { get; }
|
||||||
List<ActionsStepTelemetry> ActionsStepsTelemetry { get; }
|
List<ActionsStepTelemetry> ActionsStepsTelemetry { get; }
|
||||||
|
List<JobTelemetry> JobTelemetry { get; }
|
||||||
DictionaryContextData ExpressionValues { get; }
|
DictionaryContextData ExpressionValues { get; }
|
||||||
IList<IFunctionInfo> ExpressionFunctions { get; }
|
IList<IFunctionInfo> ExpressionFunctions { get; }
|
||||||
JobContext JobContext { get; }
|
JobContext JobContext { get; }
|
||||||
@@ -150,6 +151,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public ActionsEnvironmentReference ActionsEnvironment { get; private set; }
|
public ActionsEnvironmentReference ActionsEnvironment { get; private set; }
|
||||||
public List<ActionsStepTelemetry> ActionsStepsTelemetry { get; private set; }
|
public List<ActionsStepTelemetry> ActionsStepsTelemetry { get; private set; }
|
||||||
|
public List<JobTelemetry> JobTelemetry { get; private set; }
|
||||||
public DictionaryContextData ExpressionValues { get; } = new DictionaryContextData();
|
public DictionaryContextData ExpressionValues { get; } = new DictionaryContextData();
|
||||||
public IList<IFunctionInfo> ExpressionFunctions { get; } = new List<IFunctionInfo>();
|
public IList<IFunctionInfo> ExpressionFunctions { get; } = new List<IFunctionInfo>();
|
||||||
|
|
||||||
@@ -294,6 +296,7 @@ namespace GitHub.Runner.Worker
|
|||||||
child.ContextName = contextName;
|
child.ContextName = contextName;
|
||||||
child.EmbeddedId = embeddedId;
|
child.EmbeddedId = embeddedId;
|
||||||
child.SiblingScopeName = siblingScopeName;
|
child.SiblingScopeName = siblingScopeName;
|
||||||
|
child.JobTelemetry = JobTelemetry;
|
||||||
if (intraActionState == null)
|
if (intraActionState == null)
|
||||||
{
|
{
|
||||||
child.IntraActionState = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
child.IntraActionState = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -650,6 +653,8 @@ namespace GitHub.Runner.Worker
|
|||||||
// ActionsStepTelemetry
|
// ActionsStepTelemetry
|
||||||
ActionsStepsTelemetry = new List<ActionsStepTelemetry>();
|
ActionsStepsTelemetry = new List<ActionsStepTelemetry>();
|
||||||
|
|
||||||
|
JobTelemetry = new List<JobTelemetry>();
|
||||||
|
|
||||||
// Service container info
|
// Service container info
|
||||||
Global.ServiceContainers = new List<ContainerInfo>();
|
Global.ServiceContainers = new List<ContainerInfo>();
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ namespace GitHub.Runner.Worker
|
|||||||
"repository",
|
"repository",
|
||||||
"repository_owner",
|
"repository_owner",
|
||||||
"retention_days",
|
"retention_days",
|
||||||
|
"run_attempt",
|
||||||
"run_id",
|
"run_id",
|
||||||
"run_number",
|
"run_number",
|
||||||
"server_url",
|
"server_url",
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
{
|
{
|
||||||
// e.g. -e MY_SECRET maps the value into the exec'ed process without exposing
|
// e.g. -e MY_SECRET maps the value into the exec'ed process without exposing
|
||||||
// the value directly in the command
|
// the value directly in the command
|
||||||
dockerCommandArgs.Add($"-e {env.Key}");
|
dockerCommandArgs.Add(DockerUtil.CreateEscapedOption("-e", env.Key));
|
||||||
}
|
}
|
||||||
if (!string.IsNullOrEmpty(PrependPath))
|
if (!string.IsNullOrEmpty(PrependPath))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
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 System.Net.Http;
|
using System.Net.Http;
|
||||||
@@ -48,8 +49,8 @@ namespace GitHub.Runner.Worker
|
|||||||
Trace.Info($"Creating job server with URL: {jobServerUrl}");
|
Trace.Info($"Creating job server with URL: {jobServerUrl}");
|
||||||
// jobServerQueue is the throttling reporter.
|
// jobServerQueue is the throttling reporter.
|
||||||
_jobServerQueue = HostContext.GetService<IJobServerQueue>();
|
_jobServerQueue = HostContext.GetService<IJobServerQueue>();
|
||||||
|
VssConnection jobConnection = VssUtil.CreateConnection(jobServerUrl, jobServerCredential, new DelegatingHandler[] { new ThrottlingReportHandler(_jobServerQueue) });
|
||||||
await jobServer.ConnectAsync(jobServerUrl, jobServerCredential, new DelegatingHandler[] { new ThrottlingReportHandler(_jobServerQueue) });
|
await jobServer.ConnectAsync(jobConnection);
|
||||||
|
|
||||||
_jobServerQueue.Start(message);
|
_jobServerQueue.Start(message);
|
||||||
HostContext.WritePerfCounter($"WorkerJobServerQueueStarted_{message.RequestId.ToString()}");
|
HostContext.WritePerfCounter($"WorkerJobServerQueueStarted_{message.RequestId.ToString()}");
|
||||||
@@ -106,6 +107,9 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
jobContext.SetRunnerContext("os", VarUtil.OS);
|
jobContext.SetRunnerContext("os", VarUtil.OS);
|
||||||
|
|
||||||
|
var runnerSettings = HostContext.GetService<IConfigurationStore>().GetSettings();
|
||||||
|
jobContext.SetRunnerContext("name", runnerSettings.AgentName);
|
||||||
|
|
||||||
string toolsDirectory = HostContext.GetDirectory(WellKnownDirectory.Tools);
|
string toolsDirectory = HostContext.GetDirectory(WellKnownDirectory.Tools);
|
||||||
Directory.CreateDirectory(toolsDirectory);
|
Directory.CreateDirectory(toolsDirectory);
|
||||||
jobContext.SetRunnerContext("tool_cache", toolsDirectory);
|
jobContext.SetRunnerContext("tool_cache", toolsDirectory);
|
||||||
@@ -225,8 +229,15 @@ namespace GitHub.Runner.Worker
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load any upgrade telemetry
|
||||||
|
LoadFromTelemetryFile(jobContext.JobTelemetry);
|
||||||
|
|
||||||
|
// Make sure we don't submit secrets as telemetry
|
||||||
|
MaskTelemetrySecrets(jobContext.JobTelemetry);
|
||||||
|
|
||||||
Trace.Info("Raising job completed event.");
|
Trace.Info("Raising job completed event.");
|
||||||
var jobCompletedEvent = new JobCompletedEvent(message.RequestId, message.JobId, result, jobContext.JobOutputs, jobContext.ActionsEnvironment, jobContext.ActionsStepsTelemetry);
|
var jobCompletedEvent = new JobCompletedEvent(message.RequestId, message.JobId, result, jobContext.JobOutputs, jobContext.ActionsEnvironment, jobContext.ActionsStepsTelemetry, jobContext.JobTelemetry);
|
||||||
|
|
||||||
|
|
||||||
var completeJobRetryLimit = 5;
|
var completeJobRetryLimit = 5;
|
||||||
var exceptions = new List<Exception>();
|
var exceptions = new List<Exception>();
|
||||||
@@ -270,6 +281,38 @@ namespace GitHub.Runner.Worker
|
|||||||
throw new AggregateException(exceptions);
|
throw new AggregateException(exceptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void MaskTelemetrySecrets(List<JobTelemetry> jobTelemetry)
|
||||||
|
{
|
||||||
|
foreach (var telemetryItem in jobTelemetry)
|
||||||
|
{
|
||||||
|
telemetryItem.Message = HostContext.SecretMasker.MaskSecrets(telemetryItem.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadFromTelemetryFile(List<JobTelemetry> jobTelemetry)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var telemetryFilePath = HostContext.GetConfigFile(WellKnownConfigFile.Telemetry);
|
||||||
|
if (File.Exists(telemetryFilePath))
|
||||||
|
{
|
||||||
|
var telemetryData = File.ReadAllText(telemetryFilePath, Encoding.UTF8);
|
||||||
|
var telemetry = new JobTelemetry
|
||||||
|
{
|
||||||
|
Message = $"Runner File Telemetry:\n{telemetryData}",
|
||||||
|
Type = JobTelemetryType.General
|
||||||
|
};
|
||||||
|
jobTelemetry.Add(telemetry);
|
||||||
|
IOUtil.DeleteFile(telemetryFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Trace.Error("Error when trying to load telemetry from telemetry file");
|
||||||
|
Trace.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ShutdownQueue(bool throwOnFailure)
|
private async Task ShutdownQueue(bool throwOnFailure)
|
||||||
{
|
{
|
||||||
if (_jobServerQueue != null)
|
if (_jobServerQueue != null)
|
||||||
|
|||||||
@@ -155,6 +155,19 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
this.ActionsStepsTelemetry = actionsStepsTelemetry;
|
this.ActionsStepsTelemetry = actionsStepsTelemetry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JobCompletedEvent(
|
||||||
|
Int64 requestId,
|
||||||
|
Guid jobId,
|
||||||
|
TaskResult result,
|
||||||
|
Dictionary<String, VariableValue> outputs,
|
||||||
|
ActionsEnvironmentReference actionsEnvironment,
|
||||||
|
List<ActionsStepTelemetry> actionsStepsTelemetry,
|
||||||
|
List<JobTelemetry> jobTelemetry)
|
||||||
|
: this(requestId, jobId, result, outputs, actionsEnvironment, actionsStepsTelemetry)
|
||||||
|
{
|
||||||
|
this.JobTelemetry = jobTelemetry;
|
||||||
|
}
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
[DataMember(EmitDefaultValue = false)]
|
||||||
public Int64 RequestId
|
public Int64 RequestId
|
||||||
{
|
{
|
||||||
@@ -189,6 +202,13 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public List<JobTelemetry> JobTelemetry
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataContract]
|
[DataContract]
|
||||||
|
|||||||
17
src/Sdk/DTWebApi/WebApi/JobTelemetry.cs
Normal file
17
src/Sdk/DTWebApi/WebApi/JobTelemetry.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace GitHub.DistributedTask.WebApi
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Information about a job run on the runner
|
||||||
|
/// </summary>
|
||||||
|
[DataContract]
|
||||||
|
public class JobTelemetry
|
||||||
|
{
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public string Message { get; set; }
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public JobTelemetryType Type { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/Sdk/DTWebApi/WebApi/JobTelemetryType.cs
Normal file
13
src/Sdk/DTWebApi/WebApi/JobTelemetryType.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace GitHub.DistributedTask.WebApi
|
||||||
|
{
|
||||||
|
public enum JobTelemetryType
|
||||||
|
{
|
||||||
|
[EnumMember]
|
||||||
|
General = 0,
|
||||||
|
|
||||||
|
[EnumMember]
|
||||||
|
ActionCommand = 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -144,5 +144,54 @@ namespace GitHub.Runner.Common.Tests.Worker.Container
|
|||||||
var actual = DockerUtil.ParseRegistryHostnameFromImageName(input);
|
var actual = DockerUtil.ParseRegistryHostnameFromImageName(input);
|
||||||
Assert.Equal(expected, actual);
|
Assert.Equal(expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
[InlineData("", "")]
|
||||||
|
[InlineData("foo", "foo")]
|
||||||
|
[InlineData("foo \\ bar", "foo \\ bar")]
|
||||||
|
[InlineData("foo \\", "foo \\\\")]
|
||||||
|
[InlineData("foo \\\\", "foo \\\\\\\\")]
|
||||||
|
[InlineData("foo \\\" bar", "foo \\\\\\\" bar")]
|
||||||
|
[InlineData("foo \\\\\" bar", "foo \\\\\\\\\\\" bar")]
|
||||||
|
public void CreateEscapedOption_keyOnly(string input, string escaped)
|
||||||
|
{
|
||||||
|
var flag = "--example";
|
||||||
|
var actual = DockerUtil.CreateEscapedOption(flag, input);
|
||||||
|
string expected;
|
||||||
|
if (String.IsNullOrEmpty(input))
|
||||||
|
{
|
||||||
|
expected = "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
expected = $"{flag} \"{escaped}\"";
|
||||||
|
}
|
||||||
|
Assert.Equal(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
[InlineData("foo", "bar", "foo=bar")]
|
||||||
|
[InlineData("foo\\", "bar", "foo\\=bar")]
|
||||||
|
[InlineData("foo\\", "bar\\", "foo\\=bar\\\\")]
|
||||||
|
[InlineData("foo \\","bar \\", "foo \\=bar \\\\")]
|
||||||
|
public void CreateEscapedOption_keyValue(string keyInput, string valueInput, string escapedString)
|
||||||
|
{
|
||||||
|
var flag = "--example";
|
||||||
|
var actual = DockerUtil.CreateEscapedOption(flag, keyInput, valueInput);
|
||||||
|
string expected;
|
||||||
|
if (String.IsNullOrEmpty(keyInput))
|
||||||
|
{
|
||||||
|
expected = "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
expected = $"{flag} \"{escapedString}\"";
|
||||||
|
}
|
||||||
|
Assert.Equal(expected, actual);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,7 +161,8 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
"--work", _expectedWorkFolder,
|
"--work", _expectedWorkFolder,
|
||||||
"--auth", _expectedAuthType,
|
"--auth", _expectedAuthType,
|
||||||
"--token", _expectedToken,
|
"--token", _expectedToken,
|
||||||
"--labels", userLabels
|
"--labels", userLabels,
|
||||||
|
"--ephemeral",
|
||||||
});
|
});
|
||||||
trace.Info("Constructed.");
|
trace.Info("Constructed.");
|
||||||
_store.Setup(x => x.IsConfigured()).Returns(false);
|
_store.Setup(x => x.IsConfigured()).Returns(false);
|
||||||
@@ -179,6 +180,7 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
Assert.True(s.AgentName.Equals(_expectedAgentName));
|
Assert.True(s.AgentName.Equals(_expectedAgentName));
|
||||||
Assert.True(s.PoolId.Equals(_secondRunnerGroupId));
|
Assert.True(s.PoolId.Equals(_secondRunnerGroupId));
|
||||||
Assert.True(s.WorkFolder.Equals(_expectedWorkFolder));
|
Assert.True(s.WorkFolder.Equals(_expectedWorkFolder));
|
||||||
|
Assert.True(s.Ephemeral.Equals(true));
|
||||||
|
|
||||||
// validate GetAgentPoolsAsync gets called twice with automation pool type
|
// validate GetAgentPoolsAsync gets called twice with automation pool type
|
||||||
_runnerServer.Verify(x => x.GetAgentPoolsAsync(It.IsAny<string>(), It.Is<TaskAgentPoolType>(p => p == TaskAgentPoolType.Automation)), Times.Exactly(2));
|
_runnerServer.Verify(x => x.GetAgentPoolsAsync(It.IsAny<string>(), It.Is<TaskAgentPoolType>(p => p == TaskAgentPoolType.Automation)), Times.Exactly(2));
|
||||||
|
|||||||
@@ -264,6 +264,170 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async void RenewJobRequestNewAgentNameUpdatesSettings()
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
var oldName = "OldName";
|
||||||
|
var newName = "NewName";
|
||||||
|
var oldSettings = new RunnerSettings { AgentName = oldName };
|
||||||
|
var reservedAgent = new TaskAgentReference { Name = newName };
|
||||||
|
|
||||||
|
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestStopOnJobTokenExpiredExceptions));
|
||||||
|
TaskCompletionSource<int> firstJobRequestRenewed = new TaskCompletionSource<int>();
|
||||||
|
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
var request = new Mock<TaskAgentJobRequest>();
|
||||||
|
request.Object.ReservedAgent = reservedAgent;
|
||||||
|
PropertyInfo lockUntilProperty = request.Object.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
|
Assert.NotNull(lockUntilProperty);
|
||||||
|
lockUntilProperty.SetValue(request.Object, DateTime.UtcNow.AddMinutes(5));
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
|
_configurationStore.Setup(x => x.GetSettings()).Returns(oldSettings);
|
||||||
|
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(() =>
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
if (count < 5)
|
||||||
|
{
|
||||||
|
return Task.FromResult<TaskAgentJobRequest>(request.Object);
|
||||||
|
}
|
||||||
|
else if (count == 5 || count == 6 || count == 7)
|
||||||
|
{
|
||||||
|
throw new TimeoutException("");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cancellationTokenSource.Cancel();
|
||||||
|
return Task.FromResult<TaskAgentJobRequest>(request.Object);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var jobDispatcher = new JobDispatcher();
|
||||||
|
jobDispatcher.Initialize(hc);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await jobDispatcher.RenewJobRequestAsync(0, 0, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_configurationStore.Verify(x => x.SaveSettings(It.Is<RunnerSettings>(settings => settings.AgentName == newName)), Times.Once);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async void RenewJobRequestSameAgentNameIgnored()
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
var oldName = "OldName";
|
||||||
|
var newName = "OldName";
|
||||||
|
var oldSettings = new RunnerSettings { AgentName = oldName };
|
||||||
|
var reservedAgent = new TaskAgentReference { Name = newName };
|
||||||
|
|
||||||
|
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestStopOnJobTokenExpiredExceptions));
|
||||||
|
TaskCompletionSource<int> firstJobRequestRenewed = new TaskCompletionSource<int>();
|
||||||
|
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
var request = new Mock<TaskAgentJobRequest>();
|
||||||
|
request.Object.ReservedAgent = reservedAgent;
|
||||||
|
PropertyInfo lockUntilProperty = request.Object.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
|
Assert.NotNull(lockUntilProperty);
|
||||||
|
lockUntilProperty.SetValue(request.Object, DateTime.UtcNow.AddMinutes(5));
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
|
_configurationStore.Setup(x => x.GetSettings()).Returns(oldSettings);
|
||||||
|
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(() =>
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
if (count < 5)
|
||||||
|
{
|
||||||
|
return Task.FromResult<TaskAgentJobRequest>(request.Object);
|
||||||
|
}
|
||||||
|
else if (count == 5 || count == 6 || count == 7)
|
||||||
|
{
|
||||||
|
throw new TimeoutException("");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cancellationTokenSource.Cancel();
|
||||||
|
return Task.FromResult<TaskAgentJobRequest>(request.Object);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var jobDispatcher = new JobDispatcher();
|
||||||
|
jobDispatcher.Initialize(hc);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await jobDispatcher.RenewJobRequestAsync(0, 0, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_configurationStore.Verify(x => x.SaveSettings(It.IsAny<RunnerSettings>()), Times.Never);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async void RenewJobRequestNullAgentNameIgnored()
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
var oldName = "OldName";
|
||||||
|
var oldSettings = new RunnerSettings { AgentName = oldName };
|
||||||
|
|
||||||
|
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestStopOnJobTokenExpiredExceptions));
|
||||||
|
TaskCompletionSource<int> firstJobRequestRenewed = new TaskCompletionSource<int>();
|
||||||
|
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
var request = new Mock<TaskAgentJobRequest>();
|
||||||
|
PropertyInfo lockUntilProperty = request.Object.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
|
Assert.NotNull(lockUntilProperty);
|
||||||
|
lockUntilProperty.SetValue(request.Object, DateTime.UtcNow.AddMinutes(5));
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
|
_configurationStore.Setup(x => x.GetSettings()).Returns(oldSettings);
|
||||||
|
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(() =>
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
if (count < 5)
|
||||||
|
{
|
||||||
|
return Task.FromResult<TaskAgentJobRequest>(request.Object);
|
||||||
|
}
|
||||||
|
else if (count == 5 || count == 6 || count == 7)
|
||||||
|
{
|
||||||
|
throw new TimeoutException("");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cancellationTokenSource.Cancel();
|
||||||
|
return Task.FromResult<TaskAgentJobRequest>(request.Object);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var jobDispatcher = new JobDispatcher();
|
||||||
|
jobDispatcher.Initialize(hc);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await jobDispatcher.RenewJobRequestAsync(0, 0, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_configurationStore.Verify(x => x.SaveSettings(It.IsAny<RunnerSettings>()), Times.Never);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
|
|||||||
@@ -149,6 +149,9 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_messageListener.Verify(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()), Times.Once());
|
_messageListener.Verify(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()), Times.Once());
|
||||||
_messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once());
|
_messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once());
|
||||||
_messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.AtLeastOnce());
|
_messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.AtLeastOnce());
|
||||||
|
|
||||||
|
// verify that we didn't try to delete local settings file (since we're not ephemeral)
|
||||||
|
_configurationManager.Verify(x => x.DeleteLocalRunnerConfig(), Times.Never());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -312,6 +315,9 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_messageListener.Verify(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()), Times.Once());
|
_messageListener.Verify(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()), Times.Once());
|
||||||
_messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once());
|
_messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once());
|
||||||
_messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.AtLeastOnce());
|
_messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.AtLeastOnce());
|
||||||
|
|
||||||
|
// verify that we did try to delete local settings file (since we're ephemeral)
|
||||||
|
_configurationManager.Verify(x => x.DeleteLocalRunnerConfig(), Times.Once());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
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.Container;
|
||||||
@@ -83,6 +84,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
{
|
{
|
||||||
using (TestHostContext hc = CreateTestContext())
|
using (TestHostContext hc = CreateTestContext())
|
||||||
{
|
{
|
||||||
|
_ec.Setup(x => x.ExpressionValues).Returns(GetExpressionValues());
|
||||||
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.Returns((string tag, string line) =>
|
.Returns((string tag, string line) =>
|
||||||
{
|
{
|
||||||
@@ -105,6 +107,88 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("stop-commands", "1")]
|
||||||
|
[InlineData("", "1")]
|
||||||
|
[InlineData("set-env", "1")]
|
||||||
|
[InlineData("stop-commands", "true")]
|
||||||
|
[InlineData("", "true")]
|
||||||
|
[InlineData("set-env", "true")]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void StopProcessCommand__AllowsInvalidStopTokens__IfEnvVarIsSet(string invalidToken, string allowUnsupportedStopCommandTokens)
|
||||||
|
{
|
||||||
|
using (TestHostContext hc = CreateTestContext())
|
||||||
|
{
|
||||||
|
_ec.Object.Global.EnvironmentVariables = new Dictionary<string, string>();
|
||||||
|
var expressionValues = new DictionaryContextData
|
||||||
|
{
|
||||||
|
["env"] =
|
||||||
|
#if OS_WINDOWS
|
||||||
|
new DictionaryContextData{ { Constants.Variables.Actions.AllowUnsupportedStopCommandTokens, new StringContextData(allowUnsupportedStopCommandTokens) }}
|
||||||
|
#else
|
||||||
|
new CaseSensitiveDictionaryContextData{ { Constants.Variables.Actions.AllowUnsupportedStopCommandTokens, new StringContextData(allowUnsupportedStopCommandTokens) }}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
_ec.Setup(x => x.ExpressionValues).Returns(expressionValues);
|
||||||
|
_ec.Setup(x => x.JobTelemetry).Returns(new List<JobTelemetry>());
|
||||||
|
|
||||||
|
Assert.True(_commandManager.TryProcessCommand(_ec.Object, $"::stop-commands::{invalidToken}", null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("stop-commands")]
|
||||||
|
[InlineData("")]
|
||||||
|
[InlineData("set-env")]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void StopProcessCommand__FailOnInvalidStopTokens(string invalidToken)
|
||||||
|
{
|
||||||
|
using (TestHostContext hc = CreateTestContext())
|
||||||
|
{
|
||||||
|
_ec.Object.Global.EnvironmentVariables = new Dictionary<string, string>();
|
||||||
|
_ec.Setup(x => x.ExpressionValues).Returns(GetExpressionValues());
|
||||||
|
_ec.Setup(x => x.JobTelemetry).Returns(new List<JobTelemetry>());
|
||||||
|
Assert.Throws<Exception>(() => _commandManager.TryProcessCommand(_ec.Object, $"::stop-commands::{invalidToken}", null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void StopProcessCommandAcceptsValidToken()
|
||||||
|
{
|
||||||
|
var validToken = "randomToken";
|
||||||
|
using (TestHostContext hc = CreateTestContext())
|
||||||
|
{
|
||||||
|
_ec.Setup(x => x.ExpressionValues).Returns(GetExpressionValues());
|
||||||
|
Assert.True(_commandManager.TryProcessCommand(_ec.Object, $"::stop-commands::{validToken}", null));
|
||||||
|
Assert.False(_commandManager.TryProcessCommand(_ec.Object, "##[set-env name=foo]bar", null));
|
||||||
|
Assert.True(_commandManager.TryProcessCommand(_ec.Object, $"::{validToken}::", null));
|
||||||
|
Assert.True(_commandManager.TryProcessCommand(_ec.Object, "##[set-env name=foo]bar", null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void StopProcessCommandMasksValidTokenForEntireRun()
|
||||||
|
{
|
||||||
|
var validToken = "randomToken";
|
||||||
|
using (TestHostContext hc = CreateTestContext())
|
||||||
|
{
|
||||||
|
_ec.Setup(x => x.ExpressionValues).Returns(GetExpressionValues());
|
||||||
|
Assert.True(_commandManager.TryProcessCommand(_ec.Object, $"::stop-commands::{validToken}", null));
|
||||||
|
Assert.False(_commandManager.TryProcessCommand(_ec.Object, "##[set-env name=foo]bar", null));
|
||||||
|
Assert.Equal("***", hc.SecretMasker.MaskSecrets(validToken));
|
||||||
|
|
||||||
|
Assert.True(_commandManager.TryProcessCommand(_ec.Object, $"::{validToken}::", null));
|
||||||
|
Assert.True(_commandManager.TryProcessCommand(_ec.Object, "##[set-env name=foo]bar", null));
|
||||||
|
Assert.Equal("***", hc.SecretMasker.MaskSecrets(validToken));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
@@ -202,7 +286,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
return 1;
|
return 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
var registeredCommands = new HashSet<string>(new string[1]{ "warning" });
|
var registeredCommands = new HashSet<string>(new string[1] { "warning" });
|
||||||
ActionCommand command;
|
ActionCommand command;
|
||||||
|
|
||||||
// Columns when lines are different
|
// Columns when lines are different
|
||||||
@@ -375,5 +459,19 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
return hostContext;
|
return hostContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DictionaryContextData GetExpressionValues()
|
||||||
|
{
|
||||||
|
return new DictionaryContextData
|
||||||
|
{
|
||||||
|
["env"] =
|
||||||
|
#if OS_WINDOWS
|
||||||
|
new DictionaryContextData()
|
||||||
|
#else
|
||||||
|
new CaseSensitiveDictionaryContextData()
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.282.0
|
2.283.4
|
||||||
|
|||||||
Reference in New Issue
Block a user