mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:36:23 +00:00
Compare commits
24 Commits
releases/m
...
v2.299.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d84b751b33 | ||
|
|
e0a3781062 | ||
|
|
2e44f985bd | ||
|
|
c638ccf940 | ||
|
|
c0d21101a3 | ||
|
|
7f5067a8b5 | ||
|
|
4adaf9c1e6 | ||
|
|
d301c06a7e | ||
|
|
3e196355de | ||
|
|
dad7ad0384 | ||
|
|
b18bda773f | ||
|
|
3fc993da59 | ||
|
|
5421fe3f71 | ||
|
|
b87b4aac5c | ||
|
|
daba735b52 | ||
|
|
46ce960fd2 | ||
|
|
f4b7f91c21 | ||
|
|
ff65183e43 | ||
|
|
0f13055428 | ||
|
|
252f4de577 | ||
|
|
b6a46f2114 | ||
|
|
2145432f81 | ||
|
|
86d0ee8389 | ||
|
|
1379ed2c72 |
24
.devcontainer/devcontainer.json
Normal file
24
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||||
|
{
|
||||||
|
"name": "Actions Runner Devcontainer",
|
||||||
|
"image": "mcr.microsoft.com/devcontainers/base:focal",
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
||||||
|
"ghcr.io/devcontainers/features/dotnet": {
|
||||||
|
"version": "6.0.300"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"extensions": [
|
||||||
|
"ms-azuretools.vscode-docker",
|
||||||
|
"ms-dotnettools.csharp",
|
||||||
|
"eamodio.gitlens"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// dotnet restore to install dependencies so OmniSharp works out of the box
|
||||||
|
// src/Test restores all other projects it references, src/Runner.PluginHost is not one of them
|
||||||
|
"postCreateCommand": "dotnet restore src/Test && dotnet restore src/Runner.PluginHost",
|
||||||
|
"remoteUser": "vscode"
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
# https://editorconfig.org/
|
# https://editorconfig.org/
|
||||||
|
|
||||||
[*]
|
[*]
|
||||||
|
charset = utf-8 # Set default charset to utf-8
|
||||||
insert_final_newline = true # ensure all files end with a single newline
|
insert_final_newline = true # ensure all files end with a single newline
|
||||||
trim_trailing_whitespace = true # attempt to remove trailing whitespace on save
|
trim_trailing_whitespace = true # attempt to remove trailing whitespace on save
|
||||||
|
|
||||||
|
|||||||
25
.github/workflows/lint.yml
vendored
Normal file
25
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
name: Lint
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
# Ensure full list of changed files within `super-linter`
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Run linters
|
||||||
|
uses: github/super-linter@v4
|
||||||
|
env:
|
||||||
|
DEFAULT_BRANCH: ${{ github.base_ref }}
|
||||||
|
DISABLE_ERRORS: true
|
||||||
|
EDITORCONFIG_FILE_NAME: .editorconfig
|
||||||
|
LINTER_RULES_PATH: /src/
|
||||||
|
VALIDATE_ALL_CODEBASE: false
|
||||||
|
VALIDATE_CSHARP: true
|
||||||
9
.github/workflows/release.yml
vendored
9
.github/workflows/release.yml
vendored
@@ -247,28 +247,28 @@ jobs:
|
|||||||
const runnerVersion = fs.readFileSync('${{ github.workspace }}/src/runnerversion', 'utf8').replace(/\n$/g, '')
|
const runnerVersion = fs.readFileSync('${{ github.workspace }}/src/runnerversion', 'utf8').replace(/\n$/g, '')
|
||||||
var releaseNote = fs.readFileSync('${{ github.workspace }}/releaseNote.md', 'utf8').replace(/<RUNNER_VERSION>/g, runnerVersion)
|
var releaseNote = fs.readFileSync('${{ github.workspace }}/releaseNote.md', 'utf8').replace(/<RUNNER_VERSION>/g, runnerVersion)
|
||||||
releaseNote = releaseNote.replace(/<WIN_X64_SHA>/g, '${{needs.build.outputs.win-x64-sha}}')
|
releaseNote = releaseNote.replace(/<WIN_X64_SHA>/g, '${{needs.build.outputs.win-x64-sha}}')
|
||||||
releaseNote = releaseNote.replace(/<WIN_X64_SHA>/g, '${{needs.build.outputs.win-arm64-sha}}')
|
releaseNote = releaseNote.replace(/<WIN_ARM64_SHA>/g, '${{needs.build.outputs.win-arm64-sha}}')
|
||||||
releaseNote = releaseNote.replace(/<OSX_X64_SHA>/g, '${{needs.build.outputs.osx-x64-sha}}')
|
releaseNote = releaseNote.replace(/<OSX_X64_SHA>/g, '${{needs.build.outputs.osx-x64-sha}}')
|
||||||
releaseNote = releaseNote.replace(/<OSX_ARM64_SHA>/g, '${{needs.build.outputs.osx-arm64-sha}}')
|
releaseNote = releaseNote.replace(/<OSX_ARM64_SHA>/g, '${{needs.build.outputs.osx-arm64-sha}}')
|
||||||
releaseNote = releaseNote.replace(/<LINUX_X64_SHA>/g, '${{needs.build.outputs.linux-x64-sha}}')
|
releaseNote = releaseNote.replace(/<LINUX_X64_SHA>/g, '${{needs.build.outputs.linux-x64-sha}}')
|
||||||
releaseNote = releaseNote.replace(/<LINUX_ARM_SHA>/g, '${{needs.build.outputs.linux-arm-sha}}')
|
releaseNote = releaseNote.replace(/<LINUX_ARM_SHA>/g, '${{needs.build.outputs.linux-arm-sha}}')
|
||||||
releaseNote = releaseNote.replace(/<LINUX_ARM64_SHA>/g, '${{needs.build.outputs.linux-arm64-sha}}')
|
releaseNote = releaseNote.replace(/<LINUX_ARM64_SHA>/g, '${{needs.build.outputs.linux-arm64-sha}}')
|
||||||
releaseNote = releaseNote.replace(/<WIN_X64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.win-x64-sha-noexternals}}')
|
releaseNote = releaseNote.replace(/<WIN_X64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.win-x64-sha-noexternals}}')
|
||||||
releaseNote = releaseNote.replace(/<WIN_X64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.win-arm64-sha-noexternals}}')
|
releaseNote = releaseNote.replace(/<WIN_ARM64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.win-arm64-sha-noexternals}}')
|
||||||
releaseNote = releaseNote.replace(/<OSX_X64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.osx-x64-sha-noexternals}}')
|
releaseNote = releaseNote.replace(/<OSX_X64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.osx-x64-sha-noexternals}}')
|
||||||
releaseNote = releaseNote.replace(/<OSX_ARM64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.osx-arm64-sha-noexternals}}')
|
releaseNote = releaseNote.replace(/<OSX_ARM64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.osx-arm64-sha-noexternals}}')
|
||||||
releaseNote = releaseNote.replace(/<LINUX_X64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.linux-x64-sha-noexternals}}')
|
releaseNote = releaseNote.replace(/<LINUX_X64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.linux-x64-sha-noexternals}}')
|
||||||
releaseNote = releaseNote.replace(/<LINUX_ARM_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.linux-arm-sha-noexternals}}')
|
releaseNote = releaseNote.replace(/<LINUX_ARM_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.linux-arm-sha-noexternals}}')
|
||||||
releaseNote = releaseNote.replace(/<LINUX_ARM64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.linux-arm64-sha-noexternals}}')
|
releaseNote = releaseNote.replace(/<LINUX_ARM64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.linux-arm64-sha-noexternals}}')
|
||||||
releaseNote = releaseNote.replace(/<WIN_X64_SHA_NORUNTIME>/g, '${{needs.build.outputs.win-x64-sha-noruntime}}')
|
releaseNote = releaseNote.replace(/<WIN_X64_SHA_NORUNTIME>/g, '${{needs.build.outputs.win-x64-sha-noruntime}}')
|
||||||
releaseNote = releaseNote.replace(/<WIN_X64_SHA_NORUNTIME>/g, '${{needs.build.outputs.win-arm64-sha-noruntime}}')
|
releaseNote = releaseNote.replace(/<WIN_ARM64_SHA_NORUNTIME>/g, '${{needs.build.outputs.win-arm64-sha-noruntime}}')
|
||||||
releaseNote = releaseNote.replace(/<OSX_X64_SHA_NORUNTIME>/g, '${{needs.build.outputs.osx-x64-sha-noruntime}}')
|
releaseNote = releaseNote.replace(/<OSX_X64_SHA_NORUNTIME>/g, '${{needs.build.outputs.osx-x64-sha-noruntime}}')
|
||||||
releaseNote = releaseNote.replace(/<OSX_ARM64_SHA_NORUNTIME>/g, '${{needs.build.outputs.osx-arm64-sha-noruntime}}')
|
releaseNote = releaseNote.replace(/<OSX_ARM64_SHA_NORUNTIME>/g, '${{needs.build.outputs.osx-arm64-sha-noruntime}}')
|
||||||
releaseNote = releaseNote.replace(/<LINUX_X64_SHA_NORUNTIME>/g, '${{needs.build.outputs.linux-x64-sha-noruntime}}')
|
releaseNote = releaseNote.replace(/<LINUX_X64_SHA_NORUNTIME>/g, '${{needs.build.outputs.linux-x64-sha-noruntime}}')
|
||||||
releaseNote = releaseNote.replace(/<LINUX_ARM_SHA_NORUNTIME>/g, '${{needs.build.outputs.linux-arm-sha-noruntime}}')
|
releaseNote = releaseNote.replace(/<LINUX_ARM_SHA_NORUNTIME>/g, '${{needs.build.outputs.linux-arm-sha-noruntime}}')
|
||||||
releaseNote = releaseNote.replace(/<LINUX_ARM64_SHA_NORUNTIME>/g, '${{needs.build.outputs.linux-arm64-sha-noruntime}}')
|
releaseNote = releaseNote.replace(/<LINUX_ARM64_SHA_NORUNTIME>/g, '${{needs.build.outputs.linux-arm64-sha-noruntime}}')
|
||||||
releaseNote = releaseNote.replace(/<WIN_X64_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.win-x64-sha-noruntime-noexternals}}')
|
releaseNote = releaseNote.replace(/<WIN_X64_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.win-x64-sha-noruntime-noexternals}}')
|
||||||
releaseNote = releaseNote.replace(/<WIN_X64_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.win-arm64-sha-noruntime-noexternals}}')
|
releaseNote = releaseNote.replace(/<WIN_ARM64_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.win-arm64-sha-noruntime-noexternals}}')
|
||||||
releaseNote = releaseNote.replace(/<OSX_X64_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.osx-x64-sha-noruntime-noexternals}}')
|
releaseNote = releaseNote.replace(/<OSX_X64_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.osx-x64-sha-noruntime-noexternals}}')
|
||||||
releaseNote = releaseNote.replace(/<OSX_ARM64_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.osx-arm64-sha-noruntime-noexternals}}')
|
releaseNote = releaseNote.replace(/<OSX_ARM64_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.osx-arm64-sha-noruntime-noexternals}}')
|
||||||
releaseNote = releaseNote.replace(/<LINUX_X64_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.linux-x64-sha-noruntime-noexternals}}')
|
releaseNote = releaseNote.replace(/<LINUX_X64_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.linux-x64-sha-noruntime-noexternals}}')
|
||||||
@@ -301,6 +301,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 (full runner packages)
|
# Upload release assets (full runner packages)
|
||||||
- name: Upload Release Asset (win-x64)
|
- name: Upload Release Asset (win-x64)
|
||||||
|
|||||||
@@ -64,4 +64,4 @@ Make sure the runner has access to actions service for GitHub.com or GitHub Ente
|
|||||||
|
|
||||||
## Still not working?
|
## Still not working?
|
||||||
|
|
||||||
Contact [GitHub Support](https://support.github.com] if you have further questuons, or log an issue at https://github.com/actions/runner if you think it's a runner issue.
|
Contact [GitHub Support](https://support.github.com) if you have further questuons, or log an issue at https://github.com/actions/runner if you think it's a runner issue.
|
||||||
|
|||||||
@@ -1,11 +1,19 @@
|
|||||||
## Features
|
## Features
|
||||||
- Service containers startup error logs are now included in workflow's logs (#2110)
|
- Displays the error logs in dedicated sub-sections of the Initialize containers section (#2182)
|
||||||
|
- Add generateServiceConfig option for configure command (#2226)
|
||||||
|
- Setting debug using GitHub Action variables (#2234)
|
||||||
|
- run.sh installs SIGINT and SIGTERM traps to gracefully stop runner (#2233, 2240)
|
||||||
|
|
||||||
<!-- ## Bugs -->
|
|
||||||
|
## Bugs
|
||||||
|
- Use Global.Variables instead of JobContext and include action path/ref in the message. (#2214)
|
||||||
|
- Sanitize Windows ENVs (#2280)
|
||||||
|
|
||||||
## Misc
|
## Misc
|
||||||
- Added a feature flag to start warning on `save-state` and `set-output` deprecation (#2164)
|
- Allow '--disableupdate' in create-latest-svc.sh (#2201)
|
||||||
- Prepare supporting `vars` in workflow templates (#2096)
|
- Fix markup for support link (#2114)
|
||||||
|
- Add runner devcontainer (#2187)
|
||||||
|
- Setup linter for Runner (#2211, #2213, #2216)
|
||||||
|
|
||||||
## 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 @@
|
|||||||
<Update to ./src/runnerversion when creating release>
|
2.299.2
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ set -e
|
|||||||
|
|
||||||
flags_found=false
|
flags_found=false
|
||||||
|
|
||||||
while getopts 's:g:n:r:u:l:' opt; do
|
while getopts 's:g:n:r:u:l:d' opt; do
|
||||||
flags_found=true
|
flags_found=true
|
||||||
|
|
||||||
case $opt in
|
case $opt in
|
||||||
@@ -35,6 +35,9 @@ while getopts 's:g:n:r:u:l:' opt; do
|
|||||||
l)
|
l)
|
||||||
labels=$OPTARG
|
labels=$OPTARG
|
||||||
;;
|
;;
|
||||||
|
d)
|
||||||
|
disableupdate='true'
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo "
|
echo "
|
||||||
Runner Service Installer
|
Runner Service Installer
|
||||||
@@ -49,7 +52,8 @@ Usage:
|
|||||||
-n optional name of the runner, defaults to hostname
|
-n optional name of the runner, defaults to hostname
|
||||||
-r optional name of the runner group to add the runner to, defaults to the Default group
|
-r optional name of the runner group to add the runner to, defaults to the Default group
|
||||||
-u optional user svc will run as, defaults to current
|
-u optional user svc will run as, defaults to current
|
||||||
-l optional list of labels (split by comma) applied on the runner"
|
-l optional list of labels (split by comma) applied on the runner
|
||||||
|
-d optional allow runner to remain on the current version for one month after the release of a newer version"
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -169,8 +173,8 @@ fi
|
|||||||
|
|
||||||
echo
|
echo
|
||||||
echo "Configuring ${runner_name} @ $runner_url"
|
echo "Configuring ${runner_name} @ $runner_url"
|
||||||
echo "./config.sh --unattended --url $runner_url --token *** --name $runner_name ${labels:+--labels $labels} ${runner_group:+--runnergroup \"$runner_group\"}"
|
echo "./config.sh --unattended --url $runner_url --token *** --name $runner_name ${labels:+--labels $labels} ${runner_group:+--runnergroup \"$runner_group\"} ${disableupdate:+--disableupdate}"
|
||||||
sudo -E -u ${svc_user} ./config.sh --unattended --url $runner_url --token $RUNNER_TOKEN --name $runner_name ${labels:+--labels $labels} ${runner_group:+--runnergroup "$runner_group"}
|
sudo -E -u ${svc_user} ./config.sh --unattended --url $runner_url --token $RUNNER_TOKEN --name $runner_name ${labels:+--labels $labels} ${runner_group:+--runnergroup "$runner_group"} ${disableupdate:+--disableupdate}
|
||||||
|
|
||||||
#---------------------------------------
|
#---------------------------------------
|
||||||
# Configuring as a service
|
# Configuring as a service
|
||||||
|
|||||||
@@ -9,16 +9,52 @@ while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symli
|
|||||||
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
|
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
|
||||||
done
|
done
|
||||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||||
# run the helper process which keep the listener alive
|
|
||||||
while :;
|
run() {
|
||||||
do
|
# run the helper process which keep the listener alive
|
||||||
cp -f "$DIR"/run-helper.sh.template "$DIR"/run-helper.sh
|
while :;
|
||||||
"$DIR"/run-helper.sh $*
|
do
|
||||||
returnCode=$?
|
cp -f "$DIR"/run-helper.sh.template "$DIR"/run-helper.sh
|
||||||
if [[ $returnCode -eq 2 ]]; then
|
"$DIR"/run-helper.sh $*
|
||||||
echo "Restarting runner..."
|
returnCode=$?
|
||||||
else
|
if [[ $returnCode -eq 2 ]]; then
|
||||||
echo "Exiting runner..."
|
echo "Restarting runner..."
|
||||||
exit 0
|
else
|
||||||
fi
|
echo "Exiting runner..."
|
||||||
done
|
exit 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
runWithManualTrap() {
|
||||||
|
# Set job control
|
||||||
|
set -m
|
||||||
|
|
||||||
|
trap 'kill -INT -$PID' INT TERM
|
||||||
|
|
||||||
|
# run the helper process which keep the listener alive
|
||||||
|
while :;
|
||||||
|
do
|
||||||
|
cp -f "$DIR"/run-helper.sh.template "$DIR"/run-helper.sh
|
||||||
|
"$DIR"/run-helper.sh $* &
|
||||||
|
PID=$!
|
||||||
|
wait -f $PID
|
||||||
|
returnCode=$?
|
||||||
|
if [[ $returnCode -eq 2 ]]; then
|
||||||
|
echo "Restarting runner..."
|
||||||
|
else
|
||||||
|
echo "Exiting runner..."
|
||||||
|
# Unregister signal handling before exit
|
||||||
|
trap - INT TERM
|
||||||
|
# wait for last parts to be logged
|
||||||
|
wait $PID
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -z "$RUNNER_MANUALLY_TRAP_SIG" ]]; then
|
||||||
|
run $*
|
||||||
|
else
|
||||||
|
runWithManualTrap $*
|
||||||
|
fi
|
||||||
@@ -31,7 +31,7 @@ namespace GitHub.Runner.Common
|
|||||||
new EscapeMapping(token: "%", replacement: "%25"),
|
new EscapeMapping(token: "%", replacement: "%25"),
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly Dictionary<string, string> _properties = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
private readonly Dictionary<string, string> _properties = new(StringComparer.OrdinalIgnoreCase);
|
||||||
public const string Prefix = "##[";
|
public const string Prefix = "##[";
|
||||||
public const string _commandKey = "::";
|
public const string _commandKey = "::";
|
||||||
|
|
||||||
|
|||||||
51
src/Runner.Common/ActionsRunServer.cs
Normal file
51
src/Runner.Common/ActionsRunServer.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GitHub.DistributedTask.Pipelines;
|
||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Services.Common;
|
||||||
|
using GitHub.Services.WebApi;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Common
|
||||||
|
{
|
||||||
|
[ServiceLocator(Default = typeof(ActionsRunServer))]
|
||||||
|
public interface IActionsRunServer : IRunnerService
|
||||||
|
{
|
||||||
|
Task ConnectAsync(Uri serverUrl, VssCredentials credentials);
|
||||||
|
|
||||||
|
Task<AgentJobRequestMessage> GetJobMessageAsync(string id, CancellationToken token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ActionsRunServer : RunnerService, IActionsRunServer
|
||||||
|
{
|
||||||
|
private bool _hasConnection;
|
||||||
|
private VssConnection _connection;
|
||||||
|
private TaskAgentHttpClient _taskAgentClient;
|
||||||
|
|
||||||
|
public async Task ConnectAsync(Uri serverUrl, VssCredentials credentials)
|
||||||
|
{
|
||||||
|
_connection = await EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(100));
|
||||||
|
_taskAgentClient = _connection.GetClient<TaskAgentHttpClient>();
|
||||||
|
_hasConnection = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckConnection()
|
||||||
|
{
|
||||||
|
if (!_hasConnection)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"SetConnection");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<AgentJobRequestMessage> GetJobMessageAsync(string id, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
CheckConnection();
|
||||||
|
var jobMessage = RetryRequest<AgentJobRequestMessage>(async () =>
|
||||||
|
{
|
||||||
|
return await _taskAgentClient.GetJobMessageAsync(id, cancellationToken);
|
||||||
|
}, cancellationToken);
|
||||||
|
|
||||||
|
return jobMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -74,17 +74,18 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
Uri accountUri = new Uri(this.ServerUrl);
|
Uri accountUri = new(this.ServerUrl);
|
||||||
string repoOrOrgName = string.Empty;
|
string repoOrOrgName = string.Empty;
|
||||||
|
|
||||||
if (accountUri.Host.EndsWith(".githubusercontent.com", StringComparison.OrdinalIgnoreCase))
|
if (accountUri.Host.EndsWith(".githubusercontent.com", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(this.GitHubUrl))
|
||||||
{
|
{
|
||||||
Uri gitHubUrl = new Uri(this.GitHubUrl);
|
Uri gitHubUrl = new(this.GitHubUrl);
|
||||||
|
|
||||||
// Use the "NWO part" from the GitHub URL path
|
// Use the "NWO part" from the GitHub URL path
|
||||||
repoOrOrgName = gitHubUrl.AbsolutePath.Trim('/');
|
repoOrOrgName = gitHubUrl.AbsolutePath.Trim('/');
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (string.IsNullOrEmpty(repoOrOrgName))
|
||||||
{
|
{
|
||||||
repoOrOrgName = accountUri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
|
repoOrOrgName = accountUri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ namespace GitHub.Runner.Common
|
|||||||
public static readonly string Check = "check";
|
public static readonly string Check = "check";
|
||||||
public static readonly string Commit = "commit";
|
public static readonly string Commit = "commit";
|
||||||
public static readonly string Ephemeral = "ephemeral";
|
public static readonly string Ephemeral = "ephemeral";
|
||||||
|
public static readonly string GenerateServiceConfig = "generateServiceConfig";
|
||||||
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 DisableUpdate = "disableupdate";
|
public static readonly string DisableUpdate = "disableupdate";
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
public sealed class ExtensionManager : RunnerService, IExtensionManager
|
public sealed class ExtensionManager : RunnerService, IExtensionManager
|
||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<Type, List<IExtension>> _cache = new ConcurrentDictionary<Type, List<IExtension>>();
|
private readonly ConcurrentDictionary<Type, List<IExtension>> _cache = new();
|
||||||
|
|
||||||
public List<T> GetExtensions<T>() where T : class, IExtension
|
public List<T> GetExtensions<T>() where T : class, IExtension
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -51,12 +51,12 @@ namespace GitHub.Runner.Common
|
|||||||
private static int _defaultLogRetentionDays = 30;
|
private static int _defaultLogRetentionDays = 30;
|
||||||
private static int[] _vssHttpMethodEventIds = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 24 };
|
private static int[] _vssHttpMethodEventIds = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 24 };
|
||||||
private static int[] _vssHttpCredentialEventIds = new int[] { 11, 13, 14, 15, 16, 17, 18, 20, 21, 22, 27, 29 };
|
private static int[] _vssHttpCredentialEventIds = new int[] { 11, 13, 14, 15, 16, 17, 18, 20, 21, 22, 27, 29 };
|
||||||
private readonly ConcurrentDictionary<Type, object> _serviceInstances = new ConcurrentDictionary<Type, object>();
|
private readonly ConcurrentDictionary<Type, object> _serviceInstances = new();
|
||||||
private readonly ConcurrentDictionary<Type, Type> _serviceTypes = new ConcurrentDictionary<Type, Type>();
|
private readonly ConcurrentDictionary<Type, Type> _serviceTypes = new();
|
||||||
private readonly ISecretMasker _secretMasker = new SecretMasker();
|
private readonly ISecretMasker _secretMasker = new SecretMasker();
|
||||||
private readonly List<ProductInfoHeaderValue> _userAgents = new List<ProductInfoHeaderValue>() { new ProductInfoHeaderValue($"GitHubActionsRunner-{BuildConstants.RunnerPackage.PackageName}", BuildConstants.RunnerPackage.Version) };
|
private readonly List<ProductInfoHeaderValue> _userAgents = new() { new ProductInfoHeaderValue($"GitHubActionsRunner-{BuildConstants.RunnerPackage.PackageName}", BuildConstants.RunnerPackage.Version) };
|
||||||
private CancellationTokenSource _runnerShutdownTokenSource = new CancellationTokenSource();
|
private CancellationTokenSource _runnerShutdownTokenSource = new();
|
||||||
private object _perfLock = new object();
|
private object _perfLock = new();
|
||||||
private Tracing _trace;
|
private Tracing _trace;
|
||||||
private Tracing _actionsHttpTrace;
|
private Tracing _actionsHttpTrace;
|
||||||
private Tracing _netcoreHttpTrace;
|
private Tracing _netcoreHttpTrace;
|
||||||
@@ -66,7 +66,7 @@ namespace GitHub.Runner.Common
|
|||||||
private IDisposable _diagListenerSubscription;
|
private IDisposable _diagListenerSubscription;
|
||||||
private StartupType _startupType;
|
private StartupType _startupType;
|
||||||
private string _perfFile;
|
private string _perfFile;
|
||||||
private RunnerWebProxy _webProxy = new RunnerWebProxy();
|
private RunnerWebProxy _webProxy = new();
|
||||||
|
|
||||||
public event EventHandler Unloading;
|
public event EventHandler Unloading;
|
||||||
public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token;
|
public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token;
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
if (_enableLogRetention)
|
if (_enableLogRetention)
|
||||||
{
|
{
|
||||||
DirectoryInfo diags = new DirectoryInfo(_logFileDirectory);
|
DirectoryInfo diags = new(_logFileDirectory);
|
||||||
var logs = diags.GetFiles($"{_logFilePrefix}*.log");
|
var logs = diags.GetFiles($"{_logFilePrefix}*.log");
|
||||||
foreach (var log in logs)
|
foreach (var log in logs)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -39,19 +39,19 @@ namespace GitHub.Runner.Common
|
|||||||
private Guid _jobTimelineRecordId;
|
private Guid _jobTimelineRecordId;
|
||||||
|
|
||||||
// queue for web console line
|
// queue for web console line
|
||||||
private readonly ConcurrentQueue<ConsoleLineInfo> _webConsoleLineQueue = new ConcurrentQueue<ConsoleLineInfo>();
|
private readonly ConcurrentQueue<ConsoleLineInfo> _webConsoleLineQueue = new();
|
||||||
|
|
||||||
// queue for file upload (log file or attachment)
|
// queue for file upload (log file or attachment)
|
||||||
private readonly ConcurrentQueue<UploadFileInfo> _fileUploadQueue = new ConcurrentQueue<UploadFileInfo>();
|
private readonly ConcurrentQueue<UploadFileInfo> _fileUploadQueue = new();
|
||||||
|
|
||||||
// queue for timeline or timeline record update (one queue per timeline)
|
// queue for timeline or timeline record update (one queue per timeline)
|
||||||
private readonly ConcurrentDictionary<Guid, ConcurrentQueue<TimelineRecord>> _timelineUpdateQueue = new ConcurrentDictionary<Guid, ConcurrentQueue<TimelineRecord>>();
|
private readonly ConcurrentDictionary<Guid, ConcurrentQueue<TimelineRecord>> _timelineUpdateQueue = new();
|
||||||
|
|
||||||
// indicate how many timelines we have, we will process _timelineUpdateQueue base on the order of timeline in this list
|
// indicate how many timelines we have, we will process _timelineUpdateQueue base on the order of timeline in this list
|
||||||
private readonly List<Guid> _allTimelines = new List<Guid>();
|
private readonly List<Guid> _allTimelines = new();
|
||||||
|
|
||||||
// bufferd timeline records that fail to update
|
// bufferd timeline records that fail to update
|
||||||
private readonly Dictionary<Guid, List<TimelineRecord>> _bufferedRetryRecords = new Dictionary<Guid, List<TimelineRecord>>();
|
private readonly Dictionary<Guid, List<TimelineRecord>> _bufferedRetryRecords = new();
|
||||||
|
|
||||||
// Task for each queue's dequeue process
|
// Task for each queue's dequeue process
|
||||||
private Task _webConsoleLineDequeueTask;
|
private Task _webConsoleLineDequeueTask;
|
||||||
@@ -61,8 +61,8 @@ namespace GitHub.Runner.Common
|
|||||||
// common
|
// common
|
||||||
private IJobServer _jobServer;
|
private IJobServer _jobServer;
|
||||||
private Task[] _allDequeueTasks;
|
private Task[] _allDequeueTasks;
|
||||||
private readonly TaskCompletionSource<int> _jobCompletionSource = new TaskCompletionSource<int>();
|
private readonly TaskCompletionSource<int> _jobCompletionSource = new();
|
||||||
private readonly TaskCompletionSource<int> _jobRecordUpdated = new TaskCompletionSource<int>();
|
private readonly TaskCompletionSource<int> _jobRecordUpdated = new();
|
||||||
private bool _queueInProcess = false;
|
private bool _queueInProcess = false;
|
||||||
|
|
||||||
public TaskCompletionSource<int> JobRecordUpdated => _jobRecordUpdated;
|
public TaskCompletionSource<int> JobRecordUpdated => _jobRecordUpdated;
|
||||||
@@ -237,8 +237,8 @@ namespace GitHub.Runner.Common
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Group consolelines by timeline record of each step
|
// Group consolelines by timeline record of each step
|
||||||
Dictionary<Guid, List<TimelineRecordLogLine>> stepsConsoleLines = new Dictionary<Guid, List<TimelineRecordLogLine>>();
|
Dictionary<Guid, List<TimelineRecordLogLine>> stepsConsoleLines = new();
|
||||||
List<Guid> stepRecordIds = new List<Guid>(); // We need to keep lines in order
|
List<Guid> stepRecordIds = new(); // We need to keep lines in order
|
||||||
int linesCounter = 0;
|
int linesCounter = 0;
|
||||||
ConsoleLineInfo lineInfo;
|
ConsoleLineInfo lineInfo;
|
||||||
while (_webConsoleLineQueue.TryDequeue(out lineInfo))
|
while (_webConsoleLineQueue.TryDequeue(out lineInfo))
|
||||||
@@ -264,7 +264,7 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
// Split consolelines into batch, each batch will container at most 100 lines.
|
// Split consolelines into batch, each batch will container at most 100 lines.
|
||||||
int batchCounter = 0;
|
int batchCounter = 0;
|
||||||
List<List<TimelineRecordLogLine>> batchedLines = new List<List<TimelineRecordLogLine>>();
|
List<List<TimelineRecordLogLine>> batchedLines = new();
|
||||||
foreach (var line in stepsConsoleLines[stepRecordId])
|
foreach (var line in stepsConsoleLines[stepRecordId])
|
||||||
{
|
{
|
||||||
var currentBatch = batchedLines.ElementAtOrDefault(batchCounter);
|
var currentBatch = batchedLines.ElementAtOrDefault(batchCounter);
|
||||||
@@ -338,7 +338,7 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
while (!_jobCompletionSource.Task.IsCompleted || runOnce)
|
while (!_jobCompletionSource.Task.IsCompleted || runOnce)
|
||||||
{
|
{
|
||||||
List<UploadFileInfo> filesToUpload = new List<UploadFileInfo>();
|
List<UploadFileInfo> filesToUpload = new();
|
||||||
UploadFileInfo dequeueFile;
|
UploadFileInfo dequeueFile;
|
||||||
while (_fileUploadQueue.TryDequeue(out dequeueFile))
|
while (_fileUploadQueue.TryDequeue(out dequeueFile))
|
||||||
{
|
{
|
||||||
@@ -398,13 +398,13 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
while (!_jobCompletionSource.Task.IsCompleted || runOnce)
|
while (!_jobCompletionSource.Task.IsCompleted || runOnce)
|
||||||
{
|
{
|
||||||
List<PendingTimelineRecord> pendingUpdates = new List<PendingTimelineRecord>();
|
List<PendingTimelineRecord> pendingUpdates = new();
|
||||||
foreach (var timeline in _allTimelines)
|
foreach (var timeline in _allTimelines)
|
||||||
{
|
{
|
||||||
ConcurrentQueue<TimelineRecord> recordQueue;
|
ConcurrentQueue<TimelineRecord> recordQueue;
|
||||||
if (_timelineUpdateQueue.TryGetValue(timeline, out recordQueue))
|
if (_timelineUpdateQueue.TryGetValue(timeline, out recordQueue))
|
||||||
{
|
{
|
||||||
List<TimelineRecord> records = new List<TimelineRecord>();
|
List<TimelineRecord> records = new();
|
||||||
TimelineRecord record;
|
TimelineRecord record;
|
||||||
while (recordQueue.TryDequeue(out record))
|
while (recordQueue.TryDequeue(out record))
|
||||||
{
|
{
|
||||||
@@ -426,7 +426,7 @@ namespace GitHub.Runner.Common
|
|||||||
// we need track whether we have new sub-timeline been created on the last run.
|
// we need track whether we have new sub-timeline been created on the last run.
|
||||||
// if so, we need continue update timeline record even we on the last run.
|
// if so, we need continue update timeline record even we on the last run.
|
||||||
bool pendingSubtimelineUpdate = false;
|
bool pendingSubtimelineUpdate = false;
|
||||||
List<Exception> mainTimelineRecordsUpdateErrors = new List<Exception>();
|
List<Exception> mainTimelineRecordsUpdateErrors = new();
|
||||||
if (pendingUpdates.Count > 0)
|
if (pendingUpdates.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (var update in pendingUpdates)
|
foreach (var update in pendingUpdates)
|
||||||
@@ -529,7 +529,7 @@ namespace GitHub.Runner.Common
|
|||||||
return timelineRecords;
|
return timelineRecords;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary<Guid, TimelineRecord> dict = new Dictionary<Guid, TimelineRecord>();
|
Dictionary<Guid, TimelineRecord> dict = new();
|
||||||
foreach (TimelineRecord rec in timelineRecords)
|
foreach (TimelineRecord rec in timelineRecords)
|
||||||
{
|
{
|
||||||
if (rec == null)
|
if (rec == null)
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
public async Task<WorkerMessage> ReceiveAsync(CancellationToken cancellationToken)
|
public async Task<WorkerMessage> ReceiveAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
WorkerMessage result = new WorkerMessage(MessageType.NotInitialized, string.Empty);
|
WorkerMessage result = new(MessageType.NotInitialized, string.Empty);
|
||||||
result.MessageType = (MessageType)await _readStream.ReadInt32Async(cancellationToken);
|
result.MessageType = (MessageType)await _readStream.ReadInt32Async(cancellationToken);
|
||||||
result.Body = await _readStream.ReadStringAsync(cancellationToken);
|
result.Body = await _readStream.ReadStringAsync(cancellationToken);
|
||||||
Trace.Info($"Receiving message of length {result.Body.Length}, with hash '{IOUtil.GetSha256Hash(result.Body)}'");
|
Trace.Info($"Receiving message of length {result.Body.Length}, with hash '{IOUtil.GetSha256Hash(result.Body)}'");
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ namespace GitHub.Runner.Common
|
|||||||
public static string GetEnvironmentVariable(this Process process, IHostContext hostContext, string variable)
|
public static string GetEnvironmentVariable(this Process process, IHostContext hostContext, string variable)
|
||||||
{
|
{
|
||||||
var trace = hostContext.GetTrace(nameof(LinuxProcessExtensions));
|
var trace = hostContext.GetTrace(nameof(LinuxProcessExtensions));
|
||||||
Dictionary<string, string> env = new Dictionary<string, string>();
|
Dictionary<string, string> env = new();
|
||||||
|
|
||||||
if (Directory.Exists("/proc"))
|
if (Directory.Exists("/proc"))
|
||||||
{
|
{
|
||||||
@@ -322,8 +322,8 @@ namespace GitHub.Runner.Common
|
|||||||
// It doesn't escape '=' or ' ', so we can't parse the output into a dictionary of all envs.
|
// It doesn't escape '=' or ' ', so we can't parse the output into a dictionary of all envs.
|
||||||
// So we only look for the env you request, in the format of variable=value. (it won't work if you variable contains = or space)
|
// So we only look for the env you request, in the format of variable=value. (it won't work if you variable contains = or space)
|
||||||
trace.Info($"Read env from output of `ps e -p {process.Id} -o command`");
|
trace.Info($"Read env from output of `ps e -p {process.Id} -o command`");
|
||||||
List<string> psOut = new List<string>();
|
List<string> psOut = new();
|
||||||
object outputLock = new object();
|
object outputLock = new();
|
||||||
using (var p = hostContext.CreateService<IProcessInvoker>())
|
using (var p = hostContext.CreateService<IProcessInvoker>())
|
||||||
{
|
{
|
||||||
p.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
|
p.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.Pipelines;
|
using GitHub.DistributedTask.Pipelines;
|
||||||
@@ -6,6 +6,7 @@ using GitHub.DistributedTask.WebApi;
|
|||||||
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 Sdk.WebApi.WebApi.RawClient;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common
|
namespace GitHub.Runner.Common
|
||||||
{
|
{
|
||||||
@@ -20,42 +21,19 @@ namespace GitHub.Runner.Common
|
|||||||
public sealed class RunServer : RunnerService, IRunServer
|
public sealed class RunServer : RunnerService, IRunServer
|
||||||
{
|
{
|
||||||
private bool _hasConnection;
|
private bool _hasConnection;
|
||||||
private VssConnection _connection;
|
private Uri requestUri;
|
||||||
private TaskAgentHttpClient _taskAgentClient;
|
private RawConnection _connection;
|
||||||
|
private RunServiceHttpClient _runServiceHttpClient;
|
||||||
|
|
||||||
public async Task ConnectAsync(Uri serverUrl, VssCredentials credentials)
|
public async Task ConnectAsync(Uri serverUri, VssCredentials credentials)
|
||||||
{
|
{
|
||||||
_connection = await EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(100));
|
requestUri = serverUri;
|
||||||
_taskAgentClient = _connection.GetClient<TaskAgentHttpClient>();
|
|
||||||
|
_connection = VssUtil.CreateRawConnection(new Uri(serverUri.Authority), credentials);
|
||||||
|
_runServiceHttpClient = await _connection.GetClientAsync<RunServiceHttpClient>();
|
||||||
_hasConnection = true;
|
_hasConnection = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<VssConnection> EstablishVssConnection(Uri serverUrl, VssCredentials credentials, TimeSpan timeout)
|
|
||||||
{
|
|
||||||
Trace.Info($"EstablishVssConnection");
|
|
||||||
Trace.Info($"Establish connection with {timeout.TotalSeconds} seconds timeout.");
|
|
||||||
int attemptCount = 5;
|
|
||||||
while (attemptCount-- > 0)
|
|
||||||
{
|
|
||||||
var connection = VssUtil.CreateConnection(serverUrl, credentials, timeout: timeout);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await connection.ConnectAsync();
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
catch (Exception ex) when (attemptCount > 0)
|
|
||||||
{
|
|
||||||
Trace.Info($"Catch exception during connect. {attemptCount} attempt left.");
|
|
||||||
Trace.Error(ex);
|
|
||||||
|
|
||||||
await HostContext.Delay(TimeSpan.FromMilliseconds(100), CancellationToken.None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// should never reach here.
|
|
||||||
throw new InvalidOperationException(nameof(EstablishVssConnection));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CheckConnection()
|
private void CheckConnection()
|
||||||
{
|
{
|
||||||
if (!_hasConnection)
|
if (!_hasConnection)
|
||||||
@@ -67,37 +45,15 @@ namespace GitHub.Runner.Common
|
|||||||
public Task<AgentJobRequestMessage> GetJobMessageAsync(string id, CancellationToken cancellationToken)
|
public Task<AgentJobRequestMessage> GetJobMessageAsync(string id, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
CheckConnection();
|
CheckConnection();
|
||||||
var jobMessage = RetryRequest<AgentJobRequestMessage>(async () =>
|
var jobMessage = RetryRequest<AgentJobRequestMessage>(
|
||||||
{
|
async () => await _runServiceHttpClient.GetJobMessageAsync(requestUri, id, cancellationToken), cancellationToken);
|
||||||
return await _taskAgentClient.GetJobMessageAsync(id, cancellationToken);
|
if (jobMessage == null)
|
||||||
}, cancellationToken);
|
{
|
||||||
|
throw new TaskOrchestrationJobNotFoundException(id);
|
||||||
|
}
|
||||||
|
|
||||||
return jobMessage;
|
return jobMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<T> RetryRequest<T>(Func<Task<T>> func,
|
|
||||||
CancellationToken cancellationToken,
|
|
||||||
int maxRetryAttemptsCount = 5
|
|
||||||
)
|
|
||||||
{
|
|
||||||
var retryCount = 0;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
retryCount++;
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await func();
|
|
||||||
}
|
|
||||||
// TODO: Add handling of non-retriable exceptions: https://github.com/github/actions-broker/issues/122
|
|
||||||
catch (Exception ex) when (retryCount < maxRetryAttemptsCount)
|
|
||||||
{
|
|
||||||
Trace.Error("Catch exception during get full job message");
|
|
||||||
Trace.Error(ex);
|
|
||||||
var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15));
|
|
||||||
Trace.Warning($"Back off {backOff.TotalSeconds} seconds before next retry. {maxRetryAttemptsCount - retryCount} attempt left.");
|
|
||||||
await Task.Delay(backOff, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,31 +179,6 @@ namespace GitHub.Runner.Common
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<VssConnection> EstablishVssConnection(Uri serverUrl, VssCredentials credentials, TimeSpan timeout)
|
|
||||||
{
|
|
||||||
Trace.Info($"Establish connection with {timeout.TotalSeconds} seconds timeout.");
|
|
||||||
int attemptCount = 5;
|
|
||||||
while (attemptCount-- > 0)
|
|
||||||
{
|
|
||||||
var connection = VssUtil.CreateConnection(serverUrl, credentials, timeout: timeout);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await connection.ConnectAsync();
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
catch (Exception ex) when (attemptCount > 0)
|
|
||||||
{
|
|
||||||
Trace.Info($"Catch exception during connect. {attemptCount} attempt left.");
|
|
||||||
Trace.Error(ex);
|
|
||||||
|
|
||||||
await HostContext.Delay(TimeSpan.FromMilliseconds(100), CancellationToken.None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// should never reach here.
|
|
||||||
throw new InvalidOperationException(nameof(EstablishVssConnection));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CheckConnection(RunnerConnectionType connectionType)
|
private void CheckConnection(RunnerConnectionType connectionType)
|
||||||
{
|
{
|
||||||
switch (connectionType)
|
switch (connectionType)
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
using GitHub.Services.Common;
|
||||||
|
using GitHub.Services.WebApi;
|
||||||
|
using Sdk.WebApi.WebApi.RawClient;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common
|
namespace GitHub.Runner.Common
|
||||||
{
|
{
|
||||||
@@ -21,9 +27,9 @@ namespace GitHub.Runner.Common
|
|||||||
protected IHostContext HostContext { get; private set; }
|
protected IHostContext HostContext { get; private set; }
|
||||||
protected Tracing Trace { get; private set; }
|
protected Tracing Trace { get; private set; }
|
||||||
|
|
||||||
public string TraceName
|
public string TraceName
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return GetType().Name;
|
return GetType().Name;
|
||||||
}
|
}
|
||||||
@@ -35,5 +41,57 @@ namespace GitHub.Runner.Common
|
|||||||
Trace = HostContext.GetTrace(TraceName);
|
Trace = HostContext.GetTrace(TraceName);
|
||||||
Trace.Entering();
|
Trace.Entering();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async Task<VssConnection> EstablishVssConnection(Uri serverUrl, VssCredentials credentials, TimeSpan timeout)
|
||||||
|
{
|
||||||
|
Trace.Info($"EstablishVssConnection");
|
||||||
|
Trace.Info($"Establish connection with {timeout.TotalSeconds} seconds timeout.");
|
||||||
|
int attemptCount = 5;
|
||||||
|
while (attemptCount-- > 0)
|
||||||
|
{
|
||||||
|
var connection = VssUtil.CreateConnection(serverUrl, credentials, timeout: timeout);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await connection.ConnectAsync();
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (attemptCount > 0)
|
||||||
|
{
|
||||||
|
Trace.Info($"Catch exception during connect. {attemptCount} attempt left.");
|
||||||
|
Trace.Error(ex);
|
||||||
|
|
||||||
|
await HostContext.Delay(TimeSpan.FromMilliseconds(100), CancellationToken.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// should never reach here.
|
||||||
|
throw new InvalidOperationException(nameof(EstablishVssConnection));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task<T> RetryRequest<T>(Func<Task<T>> func,
|
||||||
|
CancellationToken cancellationToken,
|
||||||
|
int maxRetryAttemptsCount = 5
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var retryCount = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
retryCount++;
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await func();
|
||||||
|
}
|
||||||
|
// TODO: Add handling of non-retriable exceptions: https://github.com/github/actions-broker/issues/122
|
||||||
|
catch (Exception ex) when (retryCount < maxRetryAttemptsCount)
|
||||||
|
{
|
||||||
|
Trace.Error("Catch exception during get full job message");
|
||||||
|
Trace.Error(ex);
|
||||||
|
var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15));
|
||||||
|
Trace.Warning($"Back off {backOff.TotalSeconds} seconds before next retry. {maxRetryAttemptsCount - retryCount} attempt left.");
|
||||||
|
await Task.Delay(backOff, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ namespace GitHub.Runner.Common
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Trace whether a value was entered.
|
// Trace whether a value was entered.
|
||||||
string val = new String(chars.ToArray());
|
string val = new(chars.ToArray());
|
||||||
if (!string.IsNullOrEmpty(val))
|
if (!string.IsNullOrEmpty(val))
|
||||||
{
|
{
|
||||||
HostContext.SecretMasker.AddValue(val);
|
HostContext.SecretMasker.AddValue(val);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
public sealed class TraceManager : ITraceManager
|
public sealed class TraceManager : ITraceManager
|
||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<string, Tracing> _sources = new ConcurrentDictionary<string, Tracing>(StringComparer.OrdinalIgnoreCase);
|
private readonly ConcurrentDictionary<string, Tracing> _sources = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private readonly HostTraceListener _hostTraceListener;
|
private readonly HostTraceListener _hostTraceListener;
|
||||||
private TraceSetting _traceSetting;
|
private TraceSetting _traceSetting;
|
||||||
private ISecretMasker _secretMasker;
|
private ISecretMasker _secretMasker;
|
||||||
|
|||||||
@@ -347,8 +347,8 @@ namespace GitHub.Runner.Listener.Check
|
|||||||
public sealed class HttpEventSourceListener : EventListener
|
public sealed class HttpEventSourceListener : EventListener
|
||||||
{
|
{
|
||||||
private readonly List<string> _logs;
|
private readonly List<string> _logs;
|
||||||
private readonly object _lock = new object();
|
private readonly object _lock = new();
|
||||||
private readonly Dictionary<string, HashSet<string>> _ignoredEvent = new Dictionary<string, HashSet<string>>
|
private readonly Dictionary<string, HashSet<string>> _ignoredEvent = new()
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
"Microsoft-System-Net-Http",
|
"Microsoft-System-Net-Http",
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ namespace GitHub.Runner.Listener.Check
|
|||||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
|
||||||
// Request to github.com or ghes server
|
// Request to github.com or ghes server
|
||||||
Uri requestUrl = new Uri(url);
|
Uri requestUrl = new(url);
|
||||||
var env = new Dictionary<string, string>()
|
var env = new Dictionary<string, string>()
|
||||||
{
|
{
|
||||||
{ "HOSTNAME", requestUrl.Host },
|
{ "HOSTNAME", requestUrl.Host },
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
public sealed class CommandSettings
|
public sealed class CommandSettings
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, string> _envArgs = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
private readonly Dictionary<string, string> _envArgs = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private readonly CommandLineParser _parser;
|
private readonly CommandLineParser _parser;
|
||||||
private readonly IPromptManager _promptManager;
|
private readonly IPromptManager _promptManager;
|
||||||
private readonly Tracing _trace;
|
private readonly Tracing _trace;
|
||||||
@@ -26,7 +26,7 @@ namespace GitHub.Runner.Listener
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Valid flags and args for specific command - key: command, value: array of valid flags and args
|
// Valid flags and args for specific command - key: command, value: array of valid flags and args
|
||||||
private readonly Dictionary<string, string[]> validOptions = new Dictionary<string, string[]>
|
private readonly Dictionary<string, string[]> validOptions = new()
|
||||||
{
|
{
|
||||||
// Valid configure flags and args
|
// Valid configure flags and args
|
||||||
[Constants.Runner.CommandLine.Commands.Configure] =
|
[Constants.Runner.CommandLine.Commands.Configure] =
|
||||||
@@ -34,6 +34,7 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
Constants.Runner.CommandLine.Flags.DisableUpdate,
|
Constants.Runner.CommandLine.Flags.DisableUpdate,
|
||||||
Constants.Runner.CommandLine.Flags.Ephemeral,
|
Constants.Runner.CommandLine.Flags.Ephemeral,
|
||||||
|
Constants.Runner.CommandLine.Flags.GenerateServiceConfig,
|
||||||
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,
|
||||||
@@ -79,11 +80,12 @@ namespace GitHub.Runner.Listener
|
|||||||
// Flags.
|
// Flags.
|
||||||
public bool Check => TestFlag(Constants.Runner.CommandLine.Flags.Check);
|
public bool Check => TestFlag(Constants.Runner.CommandLine.Flags.Check);
|
||||||
public bool Commit => TestFlag(Constants.Runner.CommandLine.Flags.Commit);
|
public bool Commit => TestFlag(Constants.Runner.CommandLine.Flags.Commit);
|
||||||
|
public bool DisableUpdate => TestFlag(Constants.Runner.CommandLine.Flags.DisableUpdate);
|
||||||
|
public bool Ephemeral => TestFlag(Constants.Runner.CommandLine.Flags.Ephemeral);
|
||||||
|
public bool GenerateServiceConfig => TestFlag(Constants.Runner.CommandLine.Flags.GenerateServiceConfig);
|
||||||
public bool Help => TestFlag(Constants.Runner.CommandLine.Flags.Help);
|
public bool Help => TestFlag(Constants.Runner.CommandLine.Flags.Help);
|
||||||
public bool Unattended => TestFlag(Constants.Runner.CommandLine.Flags.Unattended);
|
public bool Unattended => TestFlag(Constants.Runner.CommandLine.Flags.Unattended);
|
||||||
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 DisableUpdate => TestFlag(Constants.Runner.CommandLine.Flags.DisableUpdate);
|
|
||||||
|
|
||||||
// Keep this around since customers still relies on it
|
// 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);
|
||||||
@@ -137,7 +139,7 @@ namespace GitHub.Runner.Listener
|
|||||||
// Validate commandline parser result
|
// Validate commandline parser result
|
||||||
public List<string> Validate()
|
public List<string> Validate()
|
||||||
{
|
{
|
||||||
List<string> unknowns = new List<string>();
|
List<string> unknowns = new();
|
||||||
|
|
||||||
// detect unknown commands
|
// detect unknown commands
|
||||||
unknowns.AddRange(_parser.Commands.Where(x => !validOptions.Keys.Contains(x, StringComparer.OrdinalIgnoreCase)));
|
unknowns.AddRange(_parser.Commands.Where(x => !validOptions.Keys.Contains(x, StringComparer.OrdinalIgnoreCase)));
|
||||||
|
|||||||
@@ -81,12 +81,33 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
_term.WriteLine("--------------------------------------------------------------------------------");
|
_term.WriteLine("--------------------------------------------------------------------------------");
|
||||||
|
|
||||||
Trace.Info(nameof(ConfigureAsync));
|
Trace.Info(nameof(ConfigureAsync));
|
||||||
|
|
||||||
|
if (command.GenerateServiceConfig)
|
||||||
|
{
|
||||||
|
#if OS_LINUX
|
||||||
|
if (!IsConfigured())
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("--generateServiceConfig requires that the runner is already configured. For configuring a new runner as a service, run './config.sh'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
RunnerSettings settings = _store.GetSettings();
|
||||||
|
|
||||||
|
Trace.Info($"generate service config for runner: {settings.AgentId}");
|
||||||
|
var controlManager = HostContext.GetService<ILinuxServiceControlManager>();
|
||||||
|
controlManager.GenerateScripts(settings);
|
||||||
|
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
throw new NotSupportedException("--generateServiceConfig is only supported on Linux.");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
if (IsConfigured())
|
if (IsConfigured())
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Cannot configure the runner because it is already configured. To reconfigure the runner, run 'config.cmd remove' or './config.sh remove' first.");
|
throw new InvalidOperationException("Cannot configure the runner because it is already configured. To reconfigure the runner, run 'config.cmd remove' or './config.sh remove' first.");
|
||||||
}
|
}
|
||||||
|
|
||||||
RunnerSettings runnerSettings = new RunnerSettings();
|
RunnerSettings runnerSettings = new();
|
||||||
|
|
||||||
// Loop getting url and creds until you can connect
|
// Loop getting url and creds until you can connect
|
||||||
ICredentialProvider credProvider = null;
|
ICredentialProvider credProvider = null;
|
||||||
@@ -521,7 +542,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
|
|
||||||
private TaskAgent CreateNewAgent(string agentName, RSAParameters publicKey, ISet<string> userLabels, bool ephemeral, bool disableUpdate)
|
private TaskAgent CreateNewAgent(string agentName, RSAParameters publicKey, ISet<string> userLabels, bool ephemeral, bool disableUpdate)
|
||||||
{
|
{
|
||||||
TaskAgent agent = new TaskAgent(agentName)
|
TaskAgent agent = new(agentName)
|
||||||
{
|
{
|
||||||
Authorization = new TaskAgentAuthorization
|
Authorization = new TaskAgentAuthorization
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
|
|
||||||
public class CredentialManager : RunnerService, ICredentialManager
|
public class CredentialManager : RunnerService, ICredentialManager
|
||||||
{
|
{
|
||||||
public static readonly Dictionary<string, Type> CredentialTypes = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
|
public static readonly Dictionary<string, Type> CredentialTypes = new(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
{ Constants.Configuration.OAuth, typeof(OAuthCredential)},
|
{ Constants.Configuration.OAuth, typeof(OAuthCredential)},
|
||||||
{ Constants.Configuration.OAuthAccessToken, typeof(OAuthAccessTokenCredential)},
|
{ Constants.Configuration.OAuthAccessToken, typeof(OAuthAccessTokenCredential)},
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
ArgUtil.NotNullOrEmpty(token, nameof(token));
|
ArgUtil.NotNullOrEmpty(token, nameof(token));
|
||||||
|
|
||||||
trace.Info("token retrieved: {0} chars", token.Length);
|
trace.Info("token retrieved: {0} chars", token.Length);
|
||||||
VssCredentials creds = new VssCredentials(new VssOAuthAccessTokenCredential(token), CredentialPromptType.DoNotPrompt);
|
VssCredentials creds = new(new VssOAuthAccessTokenCredential(token), CredentialPromptType.DoNotPrompt);
|
||||||
trace.Info("cred created");
|
trace.Info("cred created");
|
||||||
|
|
||||||
return creds;
|
return creds;
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
}
|
}
|
||||||
|
|
||||||
// For the service name, replace any characters outside of the alpha-numeric set and ".", "_", "-" with "-"
|
// For the service name, replace any characters outside of the alpha-numeric set and ".", "_", "-" with "-"
|
||||||
Regex regex = new Regex(@"[^0-9a-zA-Z._\-]");
|
Regex regex = new(@"[^0-9a-zA-Z._\-]");
|
||||||
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);
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ namespace GitHub.Runner.Listener
|
|||||||
// and the server will not send another job while this one is still running.
|
// and the server will not send another job while this one is still running.
|
||||||
public sealed class JobDispatcher : RunnerService, IJobDispatcher
|
public sealed class JobDispatcher : RunnerService, IJobDispatcher
|
||||||
{
|
{
|
||||||
private static Regex _invalidJsonRegex = new Regex(@"invalid\ Json\ at\ position\ '(\d+)':", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
private static Regex _invalidJsonRegex = new(@"invalid\ Json\ at\ position\ '(\d+)':", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
private readonly Lazy<Dictionary<long, TaskResult>> _localRunJobResult = new Lazy<Dictionary<long, TaskResult>>();
|
private readonly Lazy<Dictionary<long, TaskResult>> _localRunJobResult = new();
|
||||||
private int _poolId;
|
private int _poolId;
|
||||||
|
|
||||||
IConfigurationStore _configurationStore;
|
IConfigurationStore _configurationStore;
|
||||||
@@ -47,14 +47,14 @@ namespace GitHub.Runner.Listener
|
|||||||
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
|
||||||
private readonly Queue<Guid> _jobDispatchedQueue = new Queue<Guid>();
|
private readonly Queue<Guid> _jobDispatchedQueue = new();
|
||||||
private readonly ConcurrentDictionary<Guid, WorkerDispatcher> _jobInfos = new ConcurrentDictionary<Guid, WorkerDispatcher>();
|
private readonly ConcurrentDictionary<Guid, WorkerDispatcher> _jobInfos = new();
|
||||||
|
|
||||||
// allow up to 30sec for any data to be transmitted over the process channel
|
// allow up to 30sec for any data to be transmitted over the process channel
|
||||||
// timeout limit can be overwritten by environment GITHUB_ACTIONS_RUNNER_CHANNEL_TIMEOUT
|
// timeout limit can be overwritten by environment GITHUB_ACTIONS_RUNNER_CHANNEL_TIMEOUT
|
||||||
private TimeSpan _channelTimeout;
|
private TimeSpan _channelTimeout;
|
||||||
|
|
||||||
private TaskCompletionSource<bool> _runOnceJobCompleted = new TaskCompletionSource<bool>();
|
private TaskCompletionSource<bool> _runOnceJobCompleted = new();
|
||||||
|
|
||||||
public event EventHandler<JobStatusEventArgs> JobStatus;
|
public event EventHandler<JobStatusEventArgs> JobStatus;
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WorkerDispatcher newDispatch = new WorkerDispatcher(jobRequestMessage.JobId, jobRequestMessage.RequestId);
|
WorkerDispatcher newDispatch = new(jobRequestMessage.JobId, jobRequestMessage.RequestId);
|
||||||
if (runOnce)
|
if (runOnce)
|
||||||
{
|
{
|
||||||
Trace.Info("Start dispatcher for one time used runner.");
|
Trace.Info("Start dispatcher for one time used runner.");
|
||||||
@@ -357,7 +357,7 @@ namespace GitHub.Runner.Listener
|
|||||||
term.WriteLine($"{DateTime.UtcNow:u}: Running job: {message.JobDisplayName}");
|
term.WriteLine($"{DateTime.UtcNow:u}: Running job: {message.JobDisplayName}");
|
||||||
|
|
||||||
// first job request renew succeed.
|
// first job request renew succeed.
|
||||||
TaskCompletionSource<int> firstJobRequestRenewed = new TaskCompletionSource<int>();
|
TaskCompletionSource<int> firstJobRequestRenewed = new();
|
||||||
var notification = HostContext.GetService<IJobNotification>();
|
var notification = HostContext.GetService<IJobNotification>();
|
||||||
|
|
||||||
// lock renew cancellation token.
|
// lock renew cancellation token.
|
||||||
@@ -398,8 +398,8 @@ namespace GitHub.Runner.Listener
|
|||||||
HostContext.WritePerfCounter($"JobRequestRenewed_{requestId.ToString()}");
|
HostContext.WritePerfCounter($"JobRequestRenewed_{requestId.ToString()}");
|
||||||
|
|
||||||
Task<int> workerProcessTask = null;
|
Task<int> workerProcessTask = null;
|
||||||
object _outputLock = new object();
|
object _outputLock = new();
|
||||||
List<string> workerOutput = new List<string>();
|
List<string> workerOutput = new();
|
||||||
using (var processChannel = HostContext.CreateService<IProcessChannel>())
|
using (var processChannel = HostContext.CreateService<IProcessChannel>())
|
||||||
using (var processInvoker = HostContext.CreateService<IProcessInvoker>())
|
using (var processInvoker = HostContext.CreateService<IProcessInvoker>())
|
||||||
{
|
{
|
||||||
@@ -936,7 +936,7 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
var runnerServer = HostContext.GetService<IRunnerServer>();
|
var runnerServer = HostContext.GetService<IRunnerServer>();
|
||||||
int completeJobRequestRetryLimit = 5;
|
int completeJobRequestRetryLimit = 5;
|
||||||
List<Exception> exceptions = new List<Exception>();
|
List<Exception> exceptions = new();
|
||||||
while (completeJobRequestRetryLimit-- > 0)
|
while (completeJobRequestRetryLimit-- > 0)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -1039,7 +1039,7 @@ namespace GitHub.Runner.Listener
|
|||||||
public Task WorkerDispatch { get; set; }
|
public Task WorkerDispatch { get; set; }
|
||||||
public CancellationTokenSource WorkerCancellationTokenSource { get; private set; }
|
public CancellationTokenSource WorkerCancellationTokenSource { get; private set; }
|
||||||
public CancellationTokenSource WorkerCancelTimeoutKillTokenSource { get; private set; }
|
public CancellationTokenSource WorkerCancelTimeoutKillTokenSource { get; private set; }
|
||||||
private readonly object _lock = new object();
|
private readonly object _lock = new();
|
||||||
|
|
||||||
public WorkerDispatcher(Guid jobId, long requestId)
|
public WorkerDispatcher(Guid jobId, long requestId)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace GitHub.Runner.Listener
|
|||||||
private readonly TimeSpan _sessionCreationRetryInterval = TimeSpan.FromSeconds(30);
|
private readonly TimeSpan _sessionCreationRetryInterval = TimeSpan.FromSeconds(30);
|
||||||
private readonly TimeSpan _sessionConflictRetryLimit = TimeSpan.FromMinutes(4);
|
private readonly TimeSpan _sessionConflictRetryLimit = TimeSpan.FromMinutes(4);
|
||||||
private readonly TimeSpan _clockSkewRetryLimit = TimeSpan.FromMinutes(30);
|
private readonly TimeSpan _clockSkewRetryLimit = TimeSpan.FromMinutes(30);
|
||||||
private readonly Dictionary<string, int> _sessionCreationExceptionTracker = new Dictionary<string, int>();
|
private readonly Dictionary<string, int> _sessionCreationExceptionTracker = new();
|
||||||
private TaskAgentStatus runnerStatus = TaskAgentStatus.Online;
|
private TaskAgentStatus runnerStatus = TaskAgentStatus.Online;
|
||||||
private CancellationTokenSource _getMessagesTokenSource;
|
private CancellationTokenSource _getMessagesTokenSource;
|
||||||
|
|
||||||
@@ -198,7 +198,7 @@ namespace GitHub.Runner.Listener
|
|||||||
bool encounteringError = false;
|
bool encounteringError = false;
|
||||||
int continuousError = 0;
|
int continuousError = 0;
|
||||||
string errorMessage = string.Empty;
|
string errorMessage = string.Empty;
|
||||||
Stopwatch heartbeat = new Stopwatch();
|
Stopwatch heartbeat = new();
|
||||||
heartbeat.Restart();
|
heartbeat.Restart();
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace GitHub.Runner.Listener
|
|||||||
// Add environment variables from .env file
|
// Add environment variables from .env file
|
||||||
LoadAndSetEnv();
|
LoadAndSetEnv();
|
||||||
|
|
||||||
using (HostContext context = new HostContext("Runner"))
|
using (HostContext context = new("Runner"))
|
||||||
{
|
{
|
||||||
return MainAsync(context, args).GetAwaiter().GetResult();
|
return MainAsync(context, args).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ namespace GitHub.Runner.Listener
|
|||||||
private IMessageListener _listener;
|
private IMessageListener _listener;
|
||||||
private ITerminal _term;
|
private ITerminal _term;
|
||||||
private bool _inConfigStage;
|
private bool _inConfigStage;
|
||||||
private ManualResetEvent _completedCommand = new ManualResetEvent(false);
|
private ManualResetEvent _completedCommand = new(false);
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
public override void Initialize(IHostContext hostContext)
|
||||||
{
|
{
|
||||||
@@ -496,16 +496,26 @@ namespace GitHub.Runner.Listener
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var messageRef = StringUtil.ConvertFromJson<RunnerJobRequestRef>(message.Body);
|
var messageRef = StringUtil.ConvertFromJson<RunnerJobRequestRef>(message.Body);
|
||||||
|
Pipelines.AgentJobRequestMessage jobRequestMessage = null;
|
||||||
|
|
||||||
// Create connection
|
// Create connection
|
||||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
var credMgr = HostContext.GetService<ICredentialManager>();
|
||||||
var creds = credMgr.LoadCredentials();
|
var creds = credMgr.LoadCredentials();
|
||||||
|
|
||||||
var runServer = HostContext.CreateService<IRunServer>();
|
if (string.IsNullOrEmpty(messageRef.RunServiceUrl))
|
||||||
await runServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
{
|
||||||
var jobMessage = await runServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
var actionsRunServer = HostContext.CreateService<IActionsRunServer>();
|
||||||
|
await actionsRunServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
||||||
|
jobRequestMessage = await actionsRunServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var runServer = HostContext.CreateService<IRunServer>();
|
||||||
|
await runServer.ConnectAsync(new Uri(messageRef.RunServiceUrl), creds);
|
||||||
|
jobRequestMessage = await runServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
||||||
|
}
|
||||||
|
|
||||||
jobDispatcher.Run(jobMessage, runOnce);
|
jobDispatcher.Run(jobRequestMessage, runOnce);
|
||||||
if (runOnce)
|
if (runOnce)
|
||||||
{
|
{
|
||||||
Trace.Info("One time used runner received job message.");
|
Trace.Info("One time used runner received job message.");
|
||||||
|
|||||||
@@ -9,5 +9,7 @@ namespace GitHub.Runner.Listener
|
|||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
[DataMember(Name = "runner_request_id")]
|
[DataMember(Name = "runner_request_id")]
|
||||||
public string RunnerRequestId { get; set; }
|
public string RunnerRequestId { get; set; }
|
||||||
|
[DataMember(Name = "run_service_url")]
|
||||||
|
public string RunServiceUrl { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,14 +32,14 @@ namespace GitHub.Runner.Listener
|
|||||||
private static string _platform = BuildConstants.RunnerPackage.PackageName;
|
private static string _platform = BuildConstants.RunnerPackage.PackageName;
|
||||||
private static string _dotnetRuntime = "dotnetRuntime";
|
private static string _dotnetRuntime = "dotnetRuntime";
|
||||||
private static string _externals = "externals";
|
private static string _externals = "externals";
|
||||||
private readonly Dictionary<string, string> _contentHashes = new Dictionary<string, string>();
|
private readonly Dictionary<string, string> _contentHashes = new();
|
||||||
|
|
||||||
private PackageMetadata _targetPackage;
|
private PackageMetadata _targetPackage;
|
||||||
private ITerminal _terminal;
|
private ITerminal _terminal;
|
||||||
private IRunnerServer _runnerServer;
|
private IRunnerServer _runnerServer;
|
||||||
private int _poolId;
|
private int _poolId;
|
||||||
private int _agentId;
|
private int _agentId;
|
||||||
private readonly ConcurrentQueue<string> _updateTrace = new ConcurrentQueue<string>();
|
private readonly ConcurrentQueue<string> _updateTrace = new();
|
||||||
private Task _cloneAndCalculateContentHashTask;
|
private Task _cloneAndCalculateContentHashTask;
|
||||||
private string _dotnetRuntimeCloneDirectory;
|
private string _dotnetRuntimeCloneDirectory;
|
||||||
private string _externalsCloneDirectory;
|
private string _externalsCloneDirectory;
|
||||||
@@ -134,7 +134,7 @@ namespace GitHub.Runner.Listener
|
|||||||
string flagFile = "update.finished";
|
string flagFile = "update.finished";
|
||||||
IOUtil.DeleteFile(flagFile);
|
IOUtil.DeleteFile(flagFile);
|
||||||
// kick off update script
|
// kick off update script
|
||||||
Process invokeScript = new Process();
|
Process invokeScript = new();
|
||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
invokeScript.StartInfo.FileName = WhichUtil.Which("cmd.exe", trace: Trace);
|
invokeScript.StartInfo.FileName = WhichUtil.Which("cmd.exe", trace: Trace);
|
||||||
invokeScript.StartInfo.Arguments = $"/c \"{updateScript}\"";
|
invokeScript.StartInfo.Arguments = $"/c \"{updateScript}\"";
|
||||||
@@ -191,9 +191,9 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
|
|
||||||
Trace.Info($"Version '{_targetPackage.Version}' of '{_targetPackage.Type}' package available in server.");
|
Trace.Info($"Version '{_targetPackage.Version}' of '{_targetPackage.Type}' package available in server.");
|
||||||
PackageVersion serverVersion = new PackageVersion(_targetPackage.Version);
|
PackageVersion serverVersion = new(_targetPackage.Version);
|
||||||
Trace.Info($"Current running runner version is {BuildConstants.RunnerPackage.Version}");
|
Trace.Info($"Current running runner version is {BuildConstants.RunnerPackage.Version}");
|
||||||
PackageVersion runnerVersion = new PackageVersion(BuildConstants.RunnerPackage.Version);
|
PackageVersion runnerVersion = new(BuildConstants.RunnerPackage.Version);
|
||||||
|
|
||||||
return serverVersion.CompareTo(runnerVersion) > 0;
|
return serverVersion.CompareTo(runnerVersion) > 0;
|
||||||
}
|
}
|
||||||
@@ -476,7 +476,7 @@ namespace GitHub.Runner.Listener
|
|||||||
long downloadSize = 0;
|
long downloadSize = 0;
|
||||||
|
|
||||||
//open zip stream in async mode
|
//open zip stream in async mode
|
||||||
using (HttpClient httpClient = new HttpClient(HostContext.CreateHttpClientHandler()))
|
using (HttpClient httpClient = new(HostContext.CreateHttpClientHandler()))
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(_targetPackage.Token))
|
if (!string.IsNullOrEmpty(_targetPackage.Token))
|
||||||
{
|
{
|
||||||
@@ -486,7 +486,7 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
Trace.Info($"Downloading {packageDownloadUrl}");
|
Trace.Info($"Downloading {packageDownloadUrl}");
|
||||||
|
|
||||||
using (FileStream fs = new FileStream(archiveFile, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true))
|
using (FileStream fs = new(archiveFile, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true))
|
||||||
using (Stream result = await httpClient.GetStreamAsync(packageDownloadUrl))
|
using (Stream result = await httpClient.GetStreamAsync(packageDownloadUrl))
|
||||||
{
|
{
|
||||||
//81920 is the default used by System.IO.Stream.CopyTo and is under the large object heap threshold (85k).
|
//81920 is the default used by System.IO.Stream.CopyTo and is under the large object heap threshold (85k).
|
||||||
@@ -596,7 +596,7 @@ namespace GitHub.Runner.Listener
|
|||||||
int exitCode = await processInvoker.ExecuteAsync(extractDirectory, tar, $"-xzf \"{archiveFile}\"", null, token);
|
int exitCode = await processInvoker.ExecuteAsync(extractDirectory, tar, $"-xzf \"{archiveFile}\"", null, token);
|
||||||
if (exitCode != 0)
|
if (exitCode != 0)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException($"Can't use 'tar -xzf' extract archive file: {archiveFile}. return code: {exitCode}.");
|
throw new NotSupportedException($"Can't use 'tar -xzf' to extract archive file: {archiveFile}. return code: {exitCode}.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace GitHub.Runner.PluginHost
|
|||||||
{
|
{
|
||||||
public static class Program
|
public static class Program
|
||||||
{
|
{
|
||||||
private static CancellationTokenSource tokenSource = new CancellationTokenSource();
|
private static CancellationTokenSource tokenSource = new();
|
||||||
private static string executingAssemblyLocation = string.Empty;
|
private static string executingAssemblyLocation = string.Empty;
|
||||||
|
|
||||||
public static int Main(string[] args)
|
public static int Main(string[] args)
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ namespace GitHub.Runner.Plugins.Artifact
|
|||||||
string containerPath = actionsStorageArtifact.Name; // In actions storage artifacts, name equals the path
|
string containerPath = actionsStorageArtifact.Name; // In actions storage artifacts, name equals the path
|
||||||
long containerId = actionsStorageArtifact.ContainerId;
|
long containerId = actionsStorageArtifact.ContainerId;
|
||||||
|
|
||||||
FileContainerServer fileContainerServer = new FileContainerServer(context.VssConnection, projectId: new Guid(), containerId, containerPath);
|
FileContainerServer fileContainerServer = new(context.VssConnection, projectId: new Guid(), containerId, containerPath);
|
||||||
await fileContainerServer.DownloadFromContainerAsync(context, targetPath, token);
|
await fileContainerServer.DownloadFromContainerAsync(context, targetPath, token);
|
||||||
|
|
||||||
context.Output("Artifact download finished.");
|
context.Output("Artifact download finished.");
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ namespace GitHub.Runner.Plugins.Artifact
|
|||||||
//81920 is the default used by System.IO.Stream.CopyTo and is under the large object heap threshold (85k).
|
//81920 is the default used by System.IO.Stream.CopyTo and is under the large object heap threshold (85k).
|
||||||
private const int _defaultCopyBufferSize = 81920;
|
private const int _defaultCopyBufferSize = 81920;
|
||||||
|
|
||||||
private readonly ConcurrentQueue<string> _fileUploadQueue = new ConcurrentQueue<string>();
|
private readonly ConcurrentQueue<string> _fileUploadQueue = new();
|
||||||
private readonly ConcurrentQueue<DownloadInfo> _fileDownloadQueue = new ConcurrentQueue<DownloadInfo>();
|
private readonly ConcurrentQueue<DownloadInfo> _fileDownloadQueue = new();
|
||||||
private readonly ConcurrentDictionary<string, ConcurrentQueue<string>> _fileUploadTraceLog = new ConcurrentDictionary<string, ConcurrentQueue<string>>();
|
private readonly ConcurrentDictionary<string, ConcurrentQueue<string>> _fileUploadTraceLog = new();
|
||||||
private readonly ConcurrentDictionary<string, ConcurrentQueue<string>> _fileUploadProgressLog = new ConcurrentDictionary<string, ConcurrentQueue<string>>();
|
private readonly ConcurrentDictionary<string, ConcurrentQueue<string>> _fileUploadProgressLog = new();
|
||||||
private readonly FileContainerHttpClient _fileContainerHttpClient;
|
private readonly FileContainerHttpClient _fileContainerHttpClient;
|
||||||
|
|
||||||
private CancellationTokenSource _uploadCancellationTokenSource;
|
private CancellationTokenSource _uploadCancellationTokenSource;
|
||||||
@@ -67,7 +67,7 @@ namespace GitHub.Runner.Plugins.Artifact
|
|||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// Find out all container items need to be processed
|
// Find out all container items need to be processed
|
||||||
List<FileContainerItem> containerItems = new List<FileContainerItem>();
|
List<FileContainerItem> containerItems = new();
|
||||||
int retryCount = 0;
|
int retryCount = 0;
|
||||||
while (retryCount < 3)
|
while (retryCount < 3)
|
||||||
{
|
{
|
||||||
@@ -106,7 +106,7 @@ namespace GitHub.Runner.Plugins.Artifact
|
|||||||
// Create all required empty folders and emptry files, gather a list of files that we need to download from server.
|
// Create all required empty folders and emptry files, gather a list of files that we need to download from server.
|
||||||
int foldersCreated = 0;
|
int foldersCreated = 0;
|
||||||
int emptryFilesCreated = 0;
|
int emptryFilesCreated = 0;
|
||||||
List<DownloadInfo> downloadFiles = new List<DownloadInfo>();
|
List<DownloadInfo> downloadFiles = new();
|
||||||
foreach (var item in containerItems.OrderBy(x => x.Path))
|
foreach (var item in containerItems.OrderBy(x => x.Path))
|
||||||
{
|
{
|
||||||
if (!item.Path.StartsWith(_containerPath, StringComparison.OrdinalIgnoreCase))
|
if (!item.Path.StartsWith(_containerPath, StringComparison.OrdinalIgnoreCase))
|
||||||
@@ -306,7 +306,7 @@ namespace GitHub.Runner.Plugins.Artifact
|
|||||||
Task downloadMonitor = DownloadReportingAsync(context, files.Count(), token);
|
Task downloadMonitor = DownloadReportingAsync(context, files.Count(), token);
|
||||||
|
|
||||||
// Start parallel download tasks.
|
// Start parallel download tasks.
|
||||||
List<Task<DownloadResult>> parallelDownloadingTasks = new List<Task<DownloadResult>>();
|
List<Task<DownloadResult>> parallelDownloadingTasks = new();
|
||||||
for (int downloader = 0; downloader < concurrentDownloads; downloader++)
|
for (int downloader = 0; downloader < concurrentDownloads; downloader++)
|
||||||
{
|
{
|
||||||
parallelDownloadingTasks.Add(DownloadAsync(context, downloader, token));
|
parallelDownloadingTasks.Add(DownloadAsync(context, downloader, token));
|
||||||
@@ -358,7 +358,7 @@ namespace GitHub.Runner.Plugins.Artifact
|
|||||||
Task uploadMonitor = UploadReportingAsync(context, files.Count(), _uploadCancellationTokenSource.Token);
|
Task uploadMonitor = UploadReportingAsync(context, files.Count(), _uploadCancellationTokenSource.Token);
|
||||||
|
|
||||||
// Start parallel upload tasks.
|
// Start parallel upload tasks.
|
||||||
List<Task<UploadResult>> parallelUploadingTasks = new List<Task<UploadResult>>();
|
List<Task<UploadResult>> parallelUploadingTasks = new();
|
||||||
for (int uploader = 0; uploader < concurrentUploads; uploader++)
|
for (int uploader = 0; uploader < concurrentUploads; uploader++)
|
||||||
{
|
{
|
||||||
parallelUploadingTasks.Add(UploadAsync(context, uploader, _uploadCancellationTokenSource.Token));
|
parallelUploadingTasks.Add(UploadAsync(context, uploader, _uploadCancellationTokenSource.Token));
|
||||||
@@ -381,8 +381,8 @@ namespace GitHub.Runner.Plugins.Artifact
|
|||||||
|
|
||||||
private async Task<DownloadResult> DownloadAsync(RunnerActionPluginExecutionContext context, int downloaderId, CancellationToken token)
|
private async Task<DownloadResult> DownloadAsync(RunnerActionPluginExecutionContext context, int downloaderId, CancellationToken token)
|
||||||
{
|
{
|
||||||
List<DownloadInfo> failedFiles = new List<DownloadInfo>();
|
List<DownloadInfo> failedFiles = new();
|
||||||
Stopwatch downloadTimer = new Stopwatch();
|
Stopwatch downloadTimer = new();
|
||||||
while (_fileDownloadQueue.TryDequeue(out DownloadInfo fileToDownload))
|
while (_fileDownloadQueue.TryDequeue(out DownloadInfo fileToDownload))
|
||||||
{
|
{
|
||||||
token.ThrowIfCancellationRequested();
|
token.ThrowIfCancellationRequested();
|
||||||
@@ -396,7 +396,7 @@ namespace GitHub.Runner.Plugins.Artifact
|
|||||||
{
|
{
|
||||||
context.Debug($"Start downloading file: '{fileToDownload.ItemPath}' (Downloader {downloaderId})");
|
context.Debug($"Start downloading file: '{fileToDownload.ItemPath}' (Downloader {downloaderId})");
|
||||||
downloadTimer.Restart();
|
downloadTimer.Restart();
|
||||||
using (FileStream fs = new FileStream(fileToDownload.LocalPath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: _defaultFileStreamBufferSize, useAsync: true))
|
using (FileStream fs = new(fileToDownload.LocalPath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: _defaultFileStreamBufferSize, useAsync: true))
|
||||||
using (var downloadStream = await _fileContainerHttpClient.DownloadFileAsync(_containerId, fileToDownload.ItemPath, token, _projectId))
|
using (var downloadStream = await _fileContainerHttpClient.DownloadFileAsync(_containerId, fileToDownload.ItemPath, token, _projectId))
|
||||||
{
|
{
|
||||||
await downloadStream.CopyToAsync(fs, _defaultCopyBufferSize, token);
|
await downloadStream.CopyToAsync(fs, _defaultCopyBufferSize, token);
|
||||||
@@ -453,10 +453,10 @@ namespace GitHub.Runner.Plugins.Artifact
|
|||||||
|
|
||||||
private async Task<UploadResult> UploadAsync(RunnerActionPluginExecutionContext context, int uploaderId, CancellationToken token)
|
private async Task<UploadResult> UploadAsync(RunnerActionPluginExecutionContext context, int uploaderId, CancellationToken token)
|
||||||
{
|
{
|
||||||
List<string> failedFiles = new List<string>();
|
List<string> failedFiles = new();
|
||||||
long uploadedSize = 0;
|
long uploadedSize = 0;
|
||||||
string fileToUpload;
|
string fileToUpload;
|
||||||
Stopwatch uploadTimer = new Stopwatch();
|
Stopwatch uploadTimer = new();
|
||||||
while (_fileUploadQueue.TryDequeue(out fileToUpload))
|
while (_fileUploadQueue.TryDequeue(out fileToUpload))
|
||||||
{
|
{
|
||||||
token.ThrowIfCancellationRequested();
|
token.ThrowIfCancellationRequested();
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ namespace GitHub.Runner.Plugins.Artifact
|
|||||||
|
|
||||||
context.Output($"Uploading artifact '{artifactName}' from '{fullPath}' for run #{buildId}");
|
context.Output($"Uploading artifact '{artifactName}' from '{fullPath}' for run #{buildId}");
|
||||||
|
|
||||||
FileContainerServer fileContainerHelper = new FileContainerServer(context.VssConnection, projectId: Guid.Empty, containerId, artifactName);
|
FileContainerServer fileContainerHelper = new(context.VssConnection, projectId: Guid.Empty, containerId, artifactName);
|
||||||
var propertiesDictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
var propertiesDictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
long size = 0;
|
long size = 0;
|
||||||
@@ -87,7 +87,7 @@ namespace GitHub.Runner.Plugins.Artifact
|
|||||||
// Definition ID is a dummy value only used by HTTP client routing purposes
|
// Definition ID is a dummy value only used by HTTP client routing purposes
|
||||||
int definitionId = 1;
|
int definitionId = 1;
|
||||||
|
|
||||||
PipelinesServer pipelinesHelper = new PipelinesServer(context.VssConnection);
|
PipelinesServer pipelinesHelper = new(context.VssConnection);
|
||||||
|
|
||||||
var artifact = await pipelinesHelper.AssociateActionsStorageArtifactAsync(
|
var artifact = await pipelinesHelper.AssociateActionsStorageArtifactAsync(
|
||||||
definitionId,
|
definitionId,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace GitHub.Runner.Plugins.Repository
|
|||||||
#else
|
#else
|
||||||
private static readonly Encoding s_encoding = null;
|
private static readonly Encoding s_encoding = null;
|
||||||
#endif
|
#endif
|
||||||
private readonly Dictionary<string, string> gitEnv = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
private readonly Dictionary<string, string> gitEnv = new(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
{ "GIT_TERMINAL_PROMPT", "0" },
|
{ "GIT_TERMINAL_PROMPT", "0" },
|
||||||
};
|
};
|
||||||
@@ -92,11 +92,11 @@ namespace GitHub.Runner.Plugins.Repository
|
|||||||
}
|
}
|
||||||
|
|
||||||
// required 2.0, all git operation commandline args need min git version 2.0
|
// required 2.0, all git operation commandline args need min git version 2.0
|
||||||
Version minRequiredGitVersion = new Version(2, 0);
|
Version minRequiredGitVersion = new(2, 0);
|
||||||
EnsureGitVersion(minRequiredGitVersion, throwOnNotMatch: true);
|
EnsureGitVersion(minRequiredGitVersion, throwOnNotMatch: true);
|
||||||
|
|
||||||
// suggest user upgrade to 2.9 for better git experience
|
// suggest user upgrade to 2.9 for better git experience
|
||||||
Version recommendGitVersion = new Version(2, 9);
|
Version recommendGitVersion = new(2, 9);
|
||||||
if (!EnsureGitVersion(recommendGitVersion, throwOnNotMatch: false))
|
if (!EnsureGitVersion(recommendGitVersion, throwOnNotMatch: false))
|
||||||
{
|
{
|
||||||
context.Output($"To get a better Git experience, upgrade your Git to at least version '{recommendGitVersion}'. Your current Git version is '{gitVersion}'.");
|
context.Output($"To get a better Git experience, upgrade your Git to at least version '{recommendGitVersion}'. Your current Git version is '{gitVersion}'.");
|
||||||
@@ -430,7 +430,7 @@ namespace GitHub.Runner.Plugins.Repository
|
|||||||
context.Debug($"Inspect remote.origin.url for repository under {repositoryPath}");
|
context.Debug($"Inspect remote.origin.url for repository under {repositoryPath}");
|
||||||
Uri fetchUrl = null;
|
Uri fetchUrl = null;
|
||||||
|
|
||||||
List<string> outputStrings = new List<string>();
|
List<string> outputStrings = new();
|
||||||
int exitCode = await ExecuteGitCommandAsync(context, repositoryPath, "config", "--get remote.origin.url", outputStrings);
|
int exitCode = await ExecuteGitCommandAsync(context, repositoryPath, "config", "--get remote.origin.url", outputStrings);
|
||||||
|
|
||||||
if (exitCode != 0)
|
if (exitCode != 0)
|
||||||
@@ -477,7 +477,7 @@ namespace GitHub.Runner.Plugins.Repository
|
|||||||
context.Debug($"Checking git config {configKey} exist or not");
|
context.Debug($"Checking git config {configKey} exist or not");
|
||||||
|
|
||||||
// ignore any outputs by redirect them into a string list, since the output might contains secrets.
|
// ignore any outputs by redirect them into a string list, since the output might contains secrets.
|
||||||
List<string> outputStrings = new List<string>();
|
List<string> outputStrings = new();
|
||||||
int exitcode = await ExecuteGitCommandAsync(context, repositoryPath, "config", StringUtil.Format($"--get-all {configKey}"), outputStrings);
|
int exitcode = await ExecuteGitCommandAsync(context, repositoryPath, "config", StringUtil.Format($"--get-all {configKey}"), outputStrings);
|
||||||
|
|
||||||
return exitcode == 0;
|
return exitcode == 0;
|
||||||
@@ -539,7 +539,7 @@ namespace GitHub.Runner.Plugins.Repository
|
|||||||
string runnerWorkspace = context.GetRunnerContext("workspace");
|
string runnerWorkspace = context.GetRunnerContext("workspace");
|
||||||
ArgUtil.Directory(runnerWorkspace, "runnerWorkspace");
|
ArgUtil.Directory(runnerWorkspace, "runnerWorkspace");
|
||||||
Version version = null;
|
Version version = null;
|
||||||
List<string> outputStrings = new List<string>();
|
List<string> outputStrings = new();
|
||||||
int exitCode = await ExecuteGitCommandAsync(context, runnerWorkspace, "version", null, outputStrings);
|
int exitCode = await ExecuteGitCommandAsync(context, runnerWorkspace, "version", null, outputStrings);
|
||||||
context.Output($"{string.Join(Environment.NewLine, outputStrings)}");
|
context.Output($"{string.Join(Environment.NewLine, outputStrings)}");
|
||||||
if (exitCode == 0)
|
if (exitCode == 0)
|
||||||
@@ -550,7 +550,7 @@ namespace GitHub.Runner.Plugins.Repository
|
|||||||
{
|
{
|
||||||
string verString = outputStrings.First();
|
string verString = outputStrings.First();
|
||||||
// we interested about major.minor.patch version
|
// we interested about major.minor.patch version
|
||||||
Regex verRegex = new Regex("\\d+\\.\\d+(\\.\\d+)?", RegexOptions.IgnoreCase);
|
Regex verRegex = new("\\d+\\.\\d+(\\.\\d+)?", RegexOptions.IgnoreCase);
|
||||||
var matchResult = verRegex.Match(verString);
|
var matchResult = verRegex.Match(verString);
|
||||||
if (matchResult.Success && !string.IsNullOrEmpty(matchResult.Value))
|
if (matchResult.Success && !string.IsNullOrEmpty(matchResult.Value))
|
||||||
{
|
{
|
||||||
@@ -572,7 +572,7 @@ namespace GitHub.Runner.Plugins.Repository
|
|||||||
string runnerWorkspace = context.GetRunnerContext("workspace");
|
string runnerWorkspace = context.GetRunnerContext("workspace");
|
||||||
ArgUtil.Directory(runnerWorkspace, "runnerWorkspace");
|
ArgUtil.Directory(runnerWorkspace, "runnerWorkspace");
|
||||||
Version version = null;
|
Version version = null;
|
||||||
List<string> outputStrings = new List<string>();
|
List<string> outputStrings = new();
|
||||||
int exitCode = await ExecuteGitCommandAsync(context, runnerWorkspace, "lfs version", null, outputStrings);
|
int exitCode = await ExecuteGitCommandAsync(context, runnerWorkspace, "lfs version", null, outputStrings);
|
||||||
context.Output($"{string.Join(Environment.NewLine, outputStrings)}");
|
context.Output($"{string.Join(Environment.NewLine, outputStrings)}");
|
||||||
if (exitCode == 0)
|
if (exitCode == 0)
|
||||||
@@ -583,7 +583,7 @@ namespace GitHub.Runner.Plugins.Repository
|
|||||||
{
|
{
|
||||||
string verString = outputStrings.First();
|
string verString = outputStrings.First();
|
||||||
// we interested about major.minor.patch version
|
// we interested about major.minor.patch version
|
||||||
Regex verRegex = new Regex("\\d+\\.\\d+(\\.\\d+)?", RegexOptions.IgnoreCase);
|
Regex verRegex = new("\\d+\\.\\d+(\\.\\d+)?", RegexOptions.IgnoreCase);
|
||||||
var matchResult = verRegex.Match(verString);
|
var matchResult = verRegex.Match(verString);
|
||||||
if (matchResult.Success && !string.IsNullOrEmpty(matchResult.Value))
|
if (matchResult.Success && !string.IsNullOrEmpty(matchResult.Value))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
|
|||||||
private const string _remotePullRefsPrefix = "refs/remotes/pull/";
|
private const string _remotePullRefsPrefix = "refs/remotes/pull/";
|
||||||
|
|
||||||
// min git version that support add extra auth header.
|
// min git version that support add extra auth header.
|
||||||
private Version _minGitVersionSupportAuthHeader = new Version(2, 9);
|
private Version _minGitVersionSupportAuthHeader = new(2, 9);
|
||||||
|
|
||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
// min git version that support override sslBackend setting.
|
// min git version that support override sslBackend setting.
|
||||||
@@ -29,7 +29,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// min git-lfs version that support add extra auth header.
|
// min git-lfs version that support add extra auth header.
|
||||||
private Version _minGitLfsVersionSupportAuthHeader = new Version(2, 1);
|
private Version _minGitLfsVersionSupportAuthHeader = new(2, 1);
|
||||||
|
|
||||||
private void RequirementCheck(RunnerActionPluginExecutionContext executionContext, GitCliManager gitCommandManager, bool checkGitLfs)
|
private void RequirementCheck(RunnerActionPluginExecutionContext executionContext, GitCliManager gitCommandManager, bool checkGitLfs)
|
||||||
{
|
{
|
||||||
@@ -83,7 +83,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
|
|||||||
var githubUrl = executionContext.GetGitHubContext("server_url");
|
var githubUrl = executionContext.GetGitHubContext("server_url");
|
||||||
var githubUri = new Uri(!string.IsNullOrEmpty(githubUrl) ? githubUrl : "https://github.com");
|
var githubUri = new Uri(!string.IsNullOrEmpty(githubUrl) ? githubUrl : "https://github.com");
|
||||||
var portInfo = githubUri.IsDefaultPort ? string.Empty : $":{githubUri.Port}";
|
var portInfo = githubUri.IsDefaultPort ? string.Empty : $":{githubUri.Port}";
|
||||||
Uri repositoryUrl = new Uri($"{githubUri.Scheme}://{githubUri.Host}{portInfo}/{repoFullName}");
|
Uri repositoryUrl = new($"{githubUri.Scheme}://{githubUri.Host}{portInfo}/{repoFullName}");
|
||||||
if (!repositoryUrl.IsAbsoluteUri)
|
if (!repositoryUrl.IsAbsoluteUri)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Repository url need to be an absolute uri.");
|
throw new InvalidOperationException("Repository url need to be an absolute uri.");
|
||||||
@@ -121,7 +121,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
|
|||||||
executionContext.Debug($"gitLfsSupport={gitLfsSupport}");
|
executionContext.Debug($"gitLfsSupport={gitLfsSupport}");
|
||||||
|
|
||||||
// Initialize git command manager with additional environment variables.
|
// Initialize git command manager with additional environment variables.
|
||||||
Dictionary<string, string> gitEnv = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
Dictionary<string, string> gitEnv = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
// Disable prompting for git credential manager
|
// Disable prompting for git credential manager
|
||||||
gitEnv["GCM_INTERACTIVE"] = "Never";
|
gitEnv["GCM_INTERACTIVE"] = "Never";
|
||||||
@@ -141,7 +141,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
|
|||||||
gitEnv[formattedKey] = variable.Value?.Value ?? string.Empty;
|
gitEnv[formattedKey] = variable.Value?.Value ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
GitCliManager gitCommandManager = new GitCliManager(gitEnv);
|
GitCliManager gitCommandManager = new(gitEnv);
|
||||||
await gitCommandManager.LoadGitExecutionInfo(executionContext);
|
await gitCommandManager.LoadGitExecutionInfo(executionContext);
|
||||||
|
|
||||||
// Make sure the build machine met all requirements for the git repository
|
// Make sure the build machine met all requirements for the git repository
|
||||||
@@ -293,8 +293,8 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
|
|||||||
await RemoveGitConfig(executionContext, gitCommandManager, targetPath, $"http.{repositoryUrl.AbsoluteUri}.extraheader", string.Empty);
|
await RemoveGitConfig(executionContext, gitCommandManager, targetPath, $"http.{repositoryUrl.AbsoluteUri}.extraheader", string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<string> additionalFetchArgs = new List<string>();
|
List<string> additionalFetchArgs = new();
|
||||||
List<string> additionalLfsFetchArgs = new List<string>();
|
List<string> additionalLfsFetchArgs = new();
|
||||||
|
|
||||||
// add accessToken as basic auth header to handle auth challenge.
|
// add accessToken as basic auth header to handle auth challenge.
|
||||||
if (!string.IsNullOrEmpty(accessToken))
|
if (!string.IsNullOrEmpty(accessToken))
|
||||||
@@ -320,7 +320,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<string> additionalFetchSpecs = new List<string>();
|
List<string> additionalFetchSpecs = new();
|
||||||
additionalFetchSpecs.Add("+refs/heads/*:refs/remotes/origin/*");
|
additionalFetchSpecs.Add("+refs/heads/*:refs/remotes/origin/*");
|
||||||
|
|
||||||
if (IsPullRequest(sourceBranch))
|
if (IsPullRequest(sourceBranch))
|
||||||
@@ -395,7 +395,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
|
|||||||
throw new InvalidOperationException($"Git submodule sync failed with exit code: {exitCode_submoduleSync}");
|
throw new InvalidOperationException($"Git submodule sync failed with exit code: {exitCode_submoduleSync}");
|
||||||
}
|
}
|
||||||
|
|
||||||
List<string> additionalSubmoduleUpdateArgs = new List<string>();
|
List<string> additionalSubmoduleUpdateArgs = new();
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(accessToken))
|
if (!string.IsNullOrEmpty(accessToken))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
|
|||||||
{
|
{
|
||||||
public class CheckoutTask : IRunnerActionPlugin
|
public class CheckoutTask : IRunnerActionPlugin
|
||||||
{
|
{
|
||||||
private readonly Regex _validSha1 = new Regex(@"\b[0-9a-f]{40}\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled, TimeSpan.FromSeconds(2));
|
private readonly Regex _validSha1 = new(@"\b[0-9a-f]{40}\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled, TimeSpan.FromSeconds(2));
|
||||||
|
|
||||||
public async Task RunAsync(RunnerActionPluginExecutionContext executionContext, CancellationToken token)
|
public async Task RunAsync(RunnerActionPluginExecutionContext executionContext, CancellationToken token)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
|
|||||||
private const string _tagRefsPrefix = "refs/tags/";
|
private const string _tagRefsPrefix = "refs/tags/";
|
||||||
|
|
||||||
// min git version that support add extra auth header.
|
// min git version that support add extra auth header.
|
||||||
private Version _minGitVersionSupportAuthHeader = new Version(2, 9);
|
private Version _minGitVersionSupportAuthHeader = new(2, 9);
|
||||||
|
|
||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
// min git version that support override sslBackend setting.
|
// min git version that support override sslBackend setting.
|
||||||
@@ -30,7 +30,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// min git-lfs version that support add extra auth header.
|
// min git-lfs version that support add extra auth header.
|
||||||
private Version _minGitLfsVersionSupportAuthHeader = new Version(2, 1);
|
private Version _minGitLfsVersionSupportAuthHeader = new(2, 1);
|
||||||
|
|
||||||
public static string ProblemMatcher => @"
|
public static string ProblemMatcher => @"
|
||||||
{
|
{
|
||||||
@@ -62,9 +62,9 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
|
|||||||
{
|
{
|
||||||
// Validate args.
|
// Validate args.
|
||||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
||||||
Dictionary<string, string> configModifications = new Dictionary<string, string>();
|
Dictionary<string, string> configModifications = new();
|
||||||
executionContext.Output($"Syncing repository: {repoFullName}");
|
executionContext.Output($"Syncing repository: {repoFullName}");
|
||||||
Uri repositoryUrl = new Uri($"https://github.com/{repoFullName}");
|
Uri repositoryUrl = new($"https://github.com/{repoFullName}");
|
||||||
if (!repositoryUrl.IsAbsoluteUri)
|
if (!repositoryUrl.IsAbsoluteUri)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Repository url need to be an absolute uri.");
|
throw new InvalidOperationException("Repository url need to be an absolute uri.");
|
||||||
@@ -102,7 +102,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
|
|||||||
executionContext.Debug($"gitLfsSupport={gitLfsSupport}");
|
executionContext.Debug($"gitLfsSupport={gitLfsSupport}");
|
||||||
|
|
||||||
// Initialize git command manager with additional environment variables.
|
// Initialize git command manager with additional environment variables.
|
||||||
Dictionary<string, string> gitEnv = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
Dictionary<string, string> gitEnv = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
// Disable git prompt
|
// Disable git prompt
|
||||||
gitEnv["GIT_TERMINAL_PROMPT"] = "0";
|
gitEnv["GIT_TERMINAL_PROMPT"] = "0";
|
||||||
@@ -125,7 +125,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
|
|||||||
gitEnv[formattedKey] = variable.Value?.Value ?? string.Empty;
|
gitEnv[formattedKey] = variable.Value?.Value ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
GitCliManager gitCommandManager = new GitCliManager(gitEnv);
|
GitCliManager gitCommandManager = new(gitEnv);
|
||||||
await gitCommandManager.LoadGitExecutionInfo(executionContext);
|
await gitCommandManager.LoadGitExecutionInfo(executionContext);
|
||||||
|
|
||||||
// Make sure the build machine met all requirements for the git repository
|
// Make sure the build machine met all requirements for the git repository
|
||||||
@@ -277,8 +277,8 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
|
|||||||
await RemoveGitConfig(executionContext, gitCommandManager, targetPath, $"http.{repositoryUrl.AbsoluteUri}.extraheader", string.Empty);
|
await RemoveGitConfig(executionContext, gitCommandManager, targetPath, $"http.{repositoryUrl.AbsoluteUri}.extraheader", string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<string> additionalFetchArgs = new List<string>();
|
List<string> additionalFetchArgs = new();
|
||||||
List<string> additionalLfsFetchArgs = new List<string>();
|
List<string> additionalLfsFetchArgs = new();
|
||||||
|
|
||||||
// Add http.https://github.com.extraheader=... to gitconfig
|
// Add http.https://github.com.extraheader=... to gitconfig
|
||||||
// accessToken as basic auth header to handle any auth challenge from github.com
|
// accessToken as basic auth header to handle any auth challenge from github.com
|
||||||
@@ -303,7 +303,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<string> additionalFetchSpecs = new List<string>();
|
List<string> additionalFetchSpecs = new();
|
||||||
additionalFetchSpecs.Add("+refs/heads/*:refs/remotes/origin/*");
|
additionalFetchSpecs.Add("+refs/heads/*:refs/remotes/origin/*");
|
||||||
|
|
||||||
if (IsPullRequest(sourceBranch))
|
if (IsPullRequest(sourceBranch))
|
||||||
@@ -378,7 +378,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
|
|||||||
throw new InvalidOperationException($"Git submodule sync failed with exit code: {exitCode_submoduleSync}");
|
throw new InvalidOperationException($"Git submodule sync failed with exit code: {exitCode_submoduleSync}");
|
||||||
}
|
}
|
||||||
|
|
||||||
List<string> additionalSubmoduleUpdateArgs = new List<string>();
|
List<string> additionalSubmoduleUpdateArgs = new();
|
||||||
|
|
||||||
int exitCode_submoduleUpdate = await gitCommandManager.GitSubmoduleUpdate(executionContext, targetPath, fetchDepth, string.Join(" ", additionalSubmoduleUpdateArgs), checkoutNestedSubmodules, cancellationToken);
|
int exitCode_submoduleUpdate = await gitCommandManager.GitSubmoduleUpdate(executionContext, targetPath, fetchDepth, string.Join(" ", additionalSubmoduleUpdateArgs), checkoutNestedSubmodules, cancellationToken);
|
||||||
if (exitCode_submoduleUpdate != 0)
|
if (exitCode_submoduleUpdate != 0)
|
||||||
@@ -404,7 +404,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
|
|||||||
executionContext.Output($"Cleanup cached git credential from {repositoryPath}.");
|
executionContext.Output($"Cleanup cached git credential from {repositoryPath}.");
|
||||||
|
|
||||||
// Initialize git command manager
|
// Initialize git command manager
|
||||||
GitCliManager gitCommandManager = new GitCliManager();
|
GitCliManager gitCommandManager = new();
|
||||||
await gitCommandManager.LoadGitExecutionInfo(executionContext);
|
await gitCommandManager.LoadGitExecutionInfo(executionContext);
|
||||||
|
|
||||||
executionContext.Debug("Remove any extraheader setting from git config.");
|
executionContext.Debug("Remove any extraheader setting from git config.");
|
||||||
@@ -499,7 +499,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
|
|||||||
string gitConfig = Path.Combine(targetPath, ".git/config");
|
string gitConfig = Path.Combine(targetPath, ".git/config");
|
||||||
if (File.Exists(gitConfig))
|
if (File.Exists(gitConfig))
|
||||||
{
|
{
|
||||||
List<string> safeGitConfig = new List<string>();
|
List<string> safeGitConfig = new();
|
||||||
var gitConfigContents = File.ReadAllLines(gitConfig);
|
var gitConfigContents = File.ReadAllLines(gitConfig);
|
||||||
foreach (var line in gitConfigContents)
|
foreach (var line in gitConfigContents)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace GitHub.Runner.Sdk
|
|||||||
private readonly string DebugEnvironmentalVariable = "ACTIONS_STEP_DEBUG";
|
private readonly string DebugEnvironmentalVariable = "ACTIONS_STEP_DEBUG";
|
||||||
private VssConnection _connection;
|
private VssConnection _connection;
|
||||||
private RunnerWebProxy _webProxy;
|
private RunnerWebProxy _webProxy;
|
||||||
private readonly object _stdoutLock = new object();
|
private readonly object _stdoutLock = new();
|
||||||
private readonly ITraceWriter _trace; // for unit tests
|
private readonly ITraceWriter _trace; // for unit tests
|
||||||
|
|
||||||
public RunnerActionPluginExecutionContext()
|
public RunnerActionPluginExecutionContext()
|
||||||
@@ -220,7 +220,7 @@ namespace GitHub.Runner.Sdk
|
|||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Dictionary<string, string> _commandEscapeMappings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
private Dictionary<string, string> _commandEscapeMappings = new(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
";", "%3B"
|
";", "%3B"
|
||||||
|
|||||||
@@ -24,18 +24,18 @@ namespace GitHub.Runner.Sdk
|
|||||||
private Stopwatch _stopWatch;
|
private Stopwatch _stopWatch;
|
||||||
private int _asyncStreamReaderCount = 0;
|
private int _asyncStreamReaderCount = 0;
|
||||||
private bool _waitingOnStreams = false;
|
private bool _waitingOnStreams = false;
|
||||||
private readonly AsyncManualResetEvent _outputProcessEvent = new AsyncManualResetEvent();
|
private readonly AsyncManualResetEvent _outputProcessEvent = new();
|
||||||
private readonly TaskCompletionSource<bool> _processExitedCompletionSource = new TaskCompletionSource<bool>();
|
private readonly TaskCompletionSource<bool> _processExitedCompletionSource = new();
|
||||||
private readonly CancellationTokenSource _processStandardInWriteCancellationTokenSource = new CancellationTokenSource();
|
private readonly CancellationTokenSource _processStandardInWriteCancellationTokenSource = new();
|
||||||
private readonly ConcurrentQueue<string> _errorData = new ConcurrentQueue<string>();
|
private readonly ConcurrentQueue<string> _errorData = new();
|
||||||
private readonly ConcurrentQueue<string> _outputData = new ConcurrentQueue<string>();
|
private readonly ConcurrentQueue<string> _outputData = new();
|
||||||
private readonly TimeSpan _sigintTimeout = TimeSpan.FromMilliseconds(7500);
|
private readonly TimeSpan _sigintTimeout = TimeSpan.FromMilliseconds(7500);
|
||||||
private readonly TimeSpan _sigtermTimeout = TimeSpan.FromMilliseconds(2500);
|
private readonly TimeSpan _sigtermTimeout = TimeSpan.FromMilliseconds(2500);
|
||||||
private ITraceWriter Trace { get; set; }
|
private ITraceWriter Trace { get; set; }
|
||||||
|
|
||||||
private class AsyncManualResetEvent
|
private class AsyncManualResetEvent
|
||||||
{
|
{
|
||||||
private volatile TaskCompletionSource<bool> m_tcs = new TaskCompletionSource<bool>();
|
private volatile TaskCompletionSource<bool> m_tcs = new();
|
||||||
|
|
||||||
public Task WaitAsync() { return m_tcs.Task; }
|
public Task WaitAsync() { return m_tcs.Task; }
|
||||||
|
|
||||||
@@ -264,7 +264,17 @@ namespace GitHub.Runner.Sdk
|
|||||||
{
|
{
|
||||||
foreach (KeyValuePair<string, string> kvp in environment)
|
foreach (KeyValuePair<string, string> kvp in environment)
|
||||||
{
|
{
|
||||||
|
#if OS_WINDOWS
|
||||||
|
string tempKey = String.IsNullOrWhiteSpace(kvp.Key) ? kvp.Key : kvp.Key.Split('\0')[0];
|
||||||
|
string tempValue = String.IsNullOrWhiteSpace(kvp.Value) ? kvp.Value : kvp.Value.Split('\0')[0];
|
||||||
|
if(!String.IsNullOrWhiteSpace(tempKey))
|
||||||
|
{
|
||||||
|
_proc.StartInfo.Environment[tempKey] = tempValue;
|
||||||
|
}
|
||||||
|
#else
|
||||||
_proc.StartInfo.Environment[kvp.Key] = kvp.Value;
|
_proc.StartInfo.Environment[kvp.Key] = kvp.Value;
|
||||||
|
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,8 +397,8 @@ namespace GitHub.Runner.Sdk
|
|||||||
|
|
||||||
private void ProcessOutput()
|
private void ProcessOutput()
|
||||||
{
|
{
|
||||||
List<string> errorData = new List<string>();
|
List<string> errorData = new();
|
||||||
List<string> outputData = new List<string>();
|
List<string> outputData = new();
|
||||||
|
|
||||||
string errorLine;
|
string errorLine;
|
||||||
while (_errorData.TryDequeue(out errorLine))
|
while (_errorData.TryDequeue(out errorLine))
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ namespace GitHub.Runner.Sdk
|
|||||||
private string _httpsProxyPassword;
|
private string _httpsProxyPassword;
|
||||||
private string _noProxyString;
|
private string _noProxyString;
|
||||||
|
|
||||||
private readonly List<ByPassInfo> _noProxyList = new List<ByPassInfo>();
|
private readonly List<ByPassInfo> _noProxyList = new();
|
||||||
private readonly HashSet<string> _noProxyUnique = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
private readonly HashSet<string> _noProxyUnique = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private readonly Regex _validIpRegex = new Regex("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$", RegexOptions.Compiled);
|
private readonly Regex _validIpRegex = new("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$", RegexOptions.Compiled);
|
||||||
|
|
||||||
public string HttpProxyAddress => _httpProxyAddress;
|
public string HttpProxyAddress => _httpProxyAddress;
|
||||||
public string HttpProxyUsername => _httpProxyUsername;
|
public string HttpProxyUsername => _httpProxyUsername;
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ namespace GitHub.Runner.Sdk
|
|||||||
using (SHA256 sha256hash = SHA256.Create())
|
using (SHA256 sha256hash = SHA256.Create())
|
||||||
{
|
{
|
||||||
byte[] data = sha256hash.ComputeHash(Encoding.UTF8.GetBytes(hashString));
|
byte[] data = sha256hash.ComputeHash(Encoding.UTF8.GetBytes(hashString));
|
||||||
StringBuilder sBuilder = new StringBuilder();
|
StringBuilder sBuilder = new();
|
||||||
for (int i = 0; i < data.Length; i++)
|
for (int i = 0; i < data.Length; i++)
|
||||||
{
|
{
|
||||||
sBuilder.Append(data[i].ToString("x2"));
|
sBuilder.Append(data[i].ToString("x2"));
|
||||||
@@ -77,7 +77,7 @@ namespace GitHub.Runner.Sdk
|
|||||||
public static void DeleteDirectory(string path, bool contentsOnly, bool continueOnContentDeleteError, CancellationToken cancellationToken)
|
public static void DeleteDirectory(string path, bool contentsOnly, bool continueOnContentDeleteError, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNullOrEmpty(path, nameof(path));
|
ArgUtil.NotNullOrEmpty(path, nameof(path));
|
||||||
DirectoryInfo directory = new DirectoryInfo(path);
|
DirectoryInfo directory = new(path);
|
||||||
if (!directory.Exists)
|
if (!directory.Exists)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -363,12 +363,12 @@ namespace GitHub.Runner.Sdk
|
|||||||
Directory.CreateDirectory(target);
|
Directory.CreateDirectory(target);
|
||||||
|
|
||||||
// Get the file contents of the directory to copy.
|
// Get the file contents of the directory to copy.
|
||||||
DirectoryInfo sourceDir = new DirectoryInfo(source);
|
DirectoryInfo sourceDir = new(source);
|
||||||
foreach (FileInfo sourceFile in sourceDir.GetFiles() ?? new FileInfo[0])
|
foreach (FileInfo sourceFile in sourceDir.GetFiles() ?? new FileInfo[0])
|
||||||
{
|
{
|
||||||
// Check if the file already exists.
|
// Check if the file already exists.
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
FileInfo targetFile = new FileInfo(Path.Combine(target, sourceFile.Name));
|
FileInfo targetFile = new(Path.Combine(target, sourceFile.Name));
|
||||||
if (!targetFile.Exists ||
|
if (!targetFile.Exists ||
|
||||||
sourceFile.Length != targetFile.Length ||
|
sourceFile.Length != targetFile.Length ||
|
||||||
sourceFile.LastWriteTime != targetFile.LastWriteTime)
|
sourceFile.LastWriteTime != targetFile.LastWriteTime)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace GitHub.Runner.Sdk
|
|||||||
public static class StringUtil
|
public static class StringUtil
|
||||||
{
|
{
|
||||||
private static readonly object[] s_defaultFormatArgs = new object[] { null };
|
private static readonly object[] s_defaultFormatArgs = new object[] { null };
|
||||||
private static Lazy<JsonSerializerSettings> s_serializerSettings = new Lazy<JsonSerializerSettings>(() =>
|
private static Lazy<JsonSerializerSettings> s_serializerSettings = new(() =>
|
||||||
{
|
{
|
||||||
var settings = new VssJsonMediaTypeFormatter().SerializerSettings;
|
var settings = new VssJsonMediaTypeFormatter().SerializerSettings;
|
||||||
settings.DateParseHandling = DateParseHandling.None;
|
settings.DateParseHandling = DateParseHandling.None;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace GitHub.Runner.Sdk
|
|||||||
return baseUrl;
|
return baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
UriBuilder credUri = new UriBuilder(baseUrl);
|
UriBuilder credUri = new(baseUrl);
|
||||||
|
|
||||||
// ensure we have a username, uribuild will throw if username is empty but password is not.
|
// ensure we have a username, uribuild will throw if username is empty but password is not.
|
||||||
if (string.IsNullOrEmpty(username))
|
if (string.IsNullOrEmpty(username))
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using GitHub.Services.OAuth;
|
|||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using Sdk.WebApi.WebApi.RawClient;
|
||||||
|
|
||||||
namespace GitHub.Runner.Sdk
|
namespace GitHub.Runner.Sdk
|
||||||
{
|
{
|
||||||
@@ -34,7 +35,11 @@ namespace GitHub.Runner.Sdk
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static VssConnection CreateConnection(Uri serverUri, VssCredentials credentials, IEnumerable<DelegatingHandler> additionalDelegatingHandler = null, TimeSpan? timeout = null)
|
public static VssConnection CreateConnection(
|
||||||
|
Uri serverUri,
|
||||||
|
VssCredentials credentials,
|
||||||
|
IEnumerable<DelegatingHandler> additionalDelegatingHandler = null,
|
||||||
|
TimeSpan? timeout = null)
|
||||||
{
|
{
|
||||||
VssClientHttpRequestSettings settings = VssClientHttpRequestSettings.Default.Clone();
|
VssClientHttpRequestSettings settings = VssClientHttpRequestSettings.Default.Clone();
|
||||||
|
|
||||||
@@ -71,7 +76,47 @@ namespace GitHub.Runner.Sdk
|
|||||||
// settings are applied to an HttpRequestMessage.
|
// settings are applied to an HttpRequestMessage.
|
||||||
settings.AcceptLanguages.Remove(CultureInfo.InvariantCulture);
|
settings.AcceptLanguages.Remove(CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
VssConnection connection = new VssConnection(serverUri, new VssHttpMessageHandler(credentials, settings), additionalDelegatingHandler);
|
VssConnection connection = new(serverUri, new VssHttpMessageHandler(credentials, settings), additionalDelegatingHandler);
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RawConnection CreateRawConnection(
|
||||||
|
Uri serverUri,
|
||||||
|
VssCredentials credentials,
|
||||||
|
IEnumerable<DelegatingHandler> additionalDelegatingHandler = null,
|
||||||
|
TimeSpan? timeout = null)
|
||||||
|
{
|
||||||
|
RawClientHttpRequestSettings settings = RawClientHttpRequestSettings.Default.Clone();
|
||||||
|
|
||||||
|
int maxRetryRequest;
|
||||||
|
if (!int.TryParse(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_HTTP_RETRY") ?? string.Empty, out maxRetryRequest))
|
||||||
|
{
|
||||||
|
maxRetryRequest = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure MaxRetryRequest in range [3, 10]
|
||||||
|
settings.MaxRetryRequest = Math.Min(Math.Max(maxRetryRequest, 3), 10);
|
||||||
|
|
||||||
|
if (!int.TryParse(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_HTTP_TIMEOUT") ?? string.Empty, out int httpRequestTimeoutSeconds))
|
||||||
|
{
|
||||||
|
settings.SendTimeout = timeout ?? TimeSpan.FromSeconds(100);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// prefer environment variable
|
||||||
|
settings.SendTimeout = TimeSpan.FromSeconds(Math.Min(Math.Max(httpRequestTimeoutSeconds, 100), 1200));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove Invariant from the list of accepted languages.
|
||||||
|
//
|
||||||
|
// The constructor of VssHttpRequestSettings (base class of VssClientHttpRequestSettings) adds the current
|
||||||
|
// UI culture to the list of accepted languages. The UI culture will be Invariant on OSX/Linux when the
|
||||||
|
// LANG environment variable is not set when the program starts. If Invariant is in the list of accepted
|
||||||
|
// languages, then "System.ArgumentException: The value cannot be null or empty." will be thrown when the
|
||||||
|
// settings are applied to an HttpRequestMessage.
|
||||||
|
settings.AcceptLanguages.Remove(CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
RawConnection connection = new(serverUri, new RawHttpMessageHandler(credentials.ToOAuthCredentials(), settings), additionalDelegatingHandler);
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ namespace GitHub.Runner.Worker
|
|||||||
public sealed class ActionCommandManager : RunnerService, IActionCommandManager
|
public sealed class ActionCommandManager : RunnerService, IActionCommandManager
|
||||||
{
|
{
|
||||||
private const string _stopCommand = "stop-commands";
|
private const string _stopCommand = "stop-commands";
|
||||||
private readonly Dictionary<string, IActionCommandExtension> _commandExtensions = new Dictionary<string, IActionCommandExtension>(StringComparer.OrdinalIgnoreCase);
|
private readonly Dictionary<string, IActionCommandExtension> _commandExtensions = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private readonly HashSet<string> _registeredCommands = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
private readonly HashSet<string> _registeredCommands = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private readonly object _commandSerializeLock = new object();
|
private readonly object _commandSerializeLock = new();
|
||||||
private bool _stopProcessCommand = false;
|
private bool _stopProcessCommand = false;
|
||||||
private string _stopToken = null;
|
private string _stopToken = null;
|
||||||
|
|
||||||
@@ -618,7 +618,7 @@ namespace GitHub.Runner.Worker
|
|||||||
context.Debug("Enhanced Annotations not enabled on the server. The 'title', 'end_line', and 'end_column' fields are unsupported.");
|
context.Debug("Enhanced Annotations not enabled on the server. The 'title', 'end_line', and 'end_column' fields are unsupported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Issue issue = new Issue()
|
Issue issue = new()
|
||||||
{
|
{
|
||||||
Category = "General",
|
Category = "General",
|
||||||
Type = this.Type,
|
Type = this.Type,
|
||||||
|
|||||||
@@ -52,16 +52,16 @@ namespace GitHub.Runner.Worker
|
|||||||
private const int _defaultCopyBufferSize = 81920;
|
private const int _defaultCopyBufferSize = 81920;
|
||||||
private const string _dotcomApiUrl = "https://api.github.com";
|
private const string _dotcomApiUrl = "https://api.github.com";
|
||||||
|
|
||||||
private readonly Dictionary<Guid, ContainerInfo> _cachedActionContainers = new Dictionary<Guid, ContainerInfo>();
|
private readonly Dictionary<Guid, ContainerInfo> _cachedActionContainers = new();
|
||||||
public Dictionary<Guid, ContainerInfo> CachedActionContainers => _cachedActionContainers;
|
public Dictionary<Guid, ContainerInfo> CachedActionContainers => _cachedActionContainers;
|
||||||
|
|
||||||
private readonly Dictionary<Guid, List<Pipelines.ActionStep>> _cachedEmbeddedPreSteps = new Dictionary<Guid, List<Pipelines.ActionStep>>();
|
private readonly Dictionary<Guid, List<Pipelines.ActionStep>> _cachedEmbeddedPreSteps = new();
|
||||||
public Dictionary<Guid, List<Pipelines.ActionStep>> CachedEmbeddedPreSteps => _cachedEmbeddedPreSteps;
|
public Dictionary<Guid, List<Pipelines.ActionStep>> CachedEmbeddedPreSteps => _cachedEmbeddedPreSteps;
|
||||||
|
|
||||||
private readonly Dictionary<Guid, List<Guid>> _cachedEmbeddedStepIds = new Dictionary<Guid, List<Guid>>();
|
private readonly Dictionary<Guid, List<Guid>> _cachedEmbeddedStepIds = new();
|
||||||
public Dictionary<Guid, List<Guid>> CachedEmbeddedStepIds => _cachedEmbeddedStepIds;
|
public Dictionary<Guid, List<Guid>> CachedEmbeddedStepIds => _cachedEmbeddedStepIds;
|
||||||
|
|
||||||
private readonly Dictionary<Guid, Stack<Pipelines.ActionStep>> _cachedEmbeddedPostSteps = new Dictionary<Guid, Stack<Pipelines.ActionStep>>();
|
private readonly Dictionary<Guid, Stack<Pipelines.ActionStep>> _cachedEmbeddedPostSteps = new();
|
||||||
public Dictionary<Guid, Stack<Pipelines.ActionStep>> CachedEmbeddedPostSteps => _cachedEmbeddedPostSteps;
|
public Dictionary<Guid, Stack<Pipelines.ActionStep>> CachedEmbeddedPostSteps => _cachedEmbeddedPostSteps;
|
||||||
|
|
||||||
public async Task<PrepareResult> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps, Guid rootStepId = default(Guid))
|
public async Task<PrepareResult> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps, Guid rootStepId = default(Guid))
|
||||||
@@ -791,7 +791,7 @@ namespace GitHub.Runner.Worker
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
//open zip stream in async mode
|
//open zip stream in async mode
|
||||||
using (FileStream fs = new FileStream(archiveFile, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: _defaultFileStreamBufferSize, useAsync: true))
|
using (FileStream fs = new(archiveFile, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: _defaultFileStreamBufferSize, useAsync: true))
|
||||||
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
||||||
using (var httpClient = new HttpClient(httpClientHandler))
|
using (var httpClient = new HttpClient(httpClientHandler))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ namespace GitHub.Runner.Worker
|
|||||||
public ActionDefinitionData Load(IExecutionContext executionContext, string manifestFile)
|
public ActionDefinitionData Load(IExecutionContext executionContext, string manifestFile)
|
||||||
{
|
{
|
||||||
var templateContext = CreateTemplateContext(executionContext);
|
var templateContext = CreateTemplateContext(executionContext);
|
||||||
ActionDefinitionData actionDefinition = new ActionDefinitionData();
|
ActionDefinitionData actionDefinition = new();
|
||||||
|
|
||||||
// Clean up file name real quick
|
// Clean up file name real quick
|
||||||
// Instead of using Regex which can be computationally expensive,
|
// Instead of using Regex which can be computationally expensive,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
private readonly IExecutionContext _executionContext;
|
private readonly IExecutionContext _executionContext;
|
||||||
private readonly Tracing _trace;
|
private readonly Tracing _trace;
|
||||||
private readonly StringBuilder _traceBuilder = new StringBuilder();
|
private readonly StringBuilder _traceBuilder = new();
|
||||||
|
|
||||||
public string Trace => _traceBuilder.ToString();
|
public string Trace => _traceBuilder.ToString();
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace GitHub.Runner.Worker.Container
|
|||||||
private IDictionary<string, string> _userPortMappings;
|
private IDictionary<string, string> _userPortMappings;
|
||||||
private List<PortMapping> _portMappings;
|
private List<PortMapping> _portMappings;
|
||||||
private IDictionary<string, string> _environmentVariables;
|
private IDictionary<string, string> _environmentVariables;
|
||||||
private List<PathMapping> _pathMappings = new List<PathMapping>();
|
private List<PathMapping> _pathMappings = new();
|
||||||
|
|
||||||
public ContainerInfo()
|
public ContainerInfo()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ namespace GitHub.Runner.Worker.Container
|
|||||||
context.Output($"Docker client API version: {clientVersionStr}");
|
context.Output($"Docker client API version: {clientVersionStr}");
|
||||||
|
|
||||||
// we interested about major.minor.patch version
|
// we interested about major.minor.patch version
|
||||||
Regex verRegex = new Regex("\\d+\\.\\d+(\\.\\d+)?", RegexOptions.IgnoreCase);
|
Regex verRegex = new("\\d+\\.\\d+(\\.\\d+)?", RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
Version serverVersion = null;
|
Version serverVersion = null;
|
||||||
var serverVersionMatchResult = verRegex.Match(serverVersionStr);
|
var serverVersionMatchResult = verRegex.Match(serverVersionStr);
|
||||||
@@ -309,7 +309,7 @@ namespace GitHub.Runner.Worker.Container
|
|||||||
string arg = $"exec {options} {containerId} {command}".Trim();
|
string arg = $"exec {options} {containerId} {command}".Trim();
|
||||||
context.Command($"{DockerPath} {arg}");
|
context.Command($"{DockerPath} {arg}");
|
||||||
|
|
||||||
object outputLock = new object();
|
object outputLock = new();
|
||||||
var processInvoker = HostContext.CreateService<IProcessInvoker>();
|
var processInvoker = HostContext.CreateService<IProcessInvoker>();
|
||||||
processInvoker.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs message)
|
processInvoker.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs message)
|
||||||
{
|
{
|
||||||
@@ -447,7 +447,7 @@ namespace GitHub.Runner.Worker.Container
|
|||||||
string arg = $"{command} {options}".Trim();
|
string arg = $"{command} {options}".Trim();
|
||||||
context.Command($"{DockerPath} {arg}");
|
context.Command($"{DockerPath} {arg}");
|
||||||
|
|
||||||
List<string> output = new List<string>();
|
List<string> output = new();
|
||||||
var processInvoker = HostContext.CreateService<IProcessInvoker>();
|
var processInvoker = HostContext.CreateService<IProcessInvoker>();
|
||||||
processInvoker.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs message)
|
processInvoker.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs message)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ namespace GitHub.Runner.Worker.Container
|
|||||||
{
|
{
|
||||||
public class DockerUtil
|
public class DockerUtil
|
||||||
{
|
{
|
||||||
private static readonly Regex QuoteEscape = new Regex(@"(\\*)" + "\"", RegexOptions.Compiled);
|
private static readonly Regex QuoteEscape = new(@"(\\*)" + "\"", RegexOptions.Compiled);
|
||||||
private static readonly Regex EndOfStringEscape = new Regex(@"(\\+)$", RegexOptions.Compiled);
|
private static readonly Regex EndOfStringEscape = new(@"(\\+)$", RegexOptions.Compiled);
|
||||||
|
|
||||||
public static List<PortMapping> ParseDockerPort(IList<string> portMappingLines)
|
public static List<PortMapping> ParseDockerPort(IList<string> portMappingLines)
|
||||||
{
|
{
|
||||||
@@ -19,7 +19,7 @@ namespace GitHub.Runner.Worker.Container
|
|||||||
//"TARGET_PORT/PROTO -> HOST:HOST_PORT"
|
//"TARGET_PORT/PROTO -> HOST:HOST_PORT"
|
||||||
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();
|
||||||
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));
|
||||||
|
|||||||
@@ -108,9 +108,9 @@ namespace GitHub.Runner.Worker
|
|||||||
var unhealthyContainers = new List<ContainerInfo>();
|
var unhealthyContainers = new List<ContainerInfo>();
|
||||||
foreach (var container in containers.Where(c => !c.IsJobContainer))
|
foreach (var container in containers.Where(c => !c.IsJobContainer))
|
||||||
{
|
{
|
||||||
var healthcheck = await ContainerHealthcheck(executionContext, container);
|
var healthy_container = await ContainerHealthcheck(executionContext, container);
|
||||||
|
|
||||||
if (!string.Equals(healthcheck, "healthy", StringComparison.OrdinalIgnoreCase))
|
if (!healthy_container)
|
||||||
{
|
{
|
||||||
unhealthyContainers.Add(container);
|
unhealthyContainers.Add(container);
|
||||||
}
|
}
|
||||||
@@ -330,13 +330,13 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
if (!container.IsJobContainer && !container.FailedInitialization)
|
if (!container.IsJobContainer && !container.FailedInitialization)
|
||||||
{
|
{
|
||||||
executionContext.Output($"Print service container logs: {container.ContainerDisplayName}");
|
executionContext.Output($"Print service container logs: {container.ContainerDisplayName}");
|
||||||
|
|
||||||
int logsExitCode = await _dockerManager.DockerLogs(executionContext, container.ContainerId);
|
int logsExitCode = await _dockerManager.DockerLogs(executionContext, container.ContainerId);
|
||||||
if (logsExitCode != 0)
|
if (logsExitCode != 0)
|
||||||
{
|
{
|
||||||
executionContext.Warning($"Docker logs fail with exit code {logsExitCode}");
|
executionContext.Warning($"Docker logs fail with exit code {logsExitCode}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
executionContext.Output($"Stop and remove container: {container.ContainerDisplayName}");
|
executionContext.Output($"Stop and remove container: {container.ContainerDisplayName}");
|
||||||
@@ -354,8 +354,8 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
context.Command($"{command} {arg}");
|
context.Command($"{command} {arg}");
|
||||||
|
|
||||||
List<string> outputs = new List<string>();
|
List<string> outputs = new();
|
||||||
object outputLock = new object();
|
object outputLock = new();
|
||||||
var processInvoker = HostContext.CreateService<IProcessInvoker>();
|
var processInvoker = HostContext.CreateService<IProcessInvoker>();
|
||||||
processInvoker.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs message)
|
processInvoker.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs message)
|
||||||
{
|
{
|
||||||
@@ -423,14 +423,14 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> ContainerHealthcheck(IExecutionContext executionContext, ContainerInfo container)
|
private async Task<bool> ContainerHealthcheck(IExecutionContext executionContext, ContainerInfo container)
|
||||||
{
|
{
|
||||||
string healthCheck = "--format=\"{{if .Config.Healthcheck}}{{print .State.Health.Status}}{{end}}\"";
|
string healthCheck = "--format=\"{{if .Config.Healthcheck}}{{print .State.Health.Status}}{{end}}\"";
|
||||||
string serviceHealth = (await _dockerManager.DockerInspect(context: executionContext, dockerObject: container.ContainerId, options: healthCheck)).FirstOrDefault();
|
string serviceHealth = (await _dockerManager.DockerInspect(context: executionContext, dockerObject: container.ContainerId, options: healthCheck)).FirstOrDefault();
|
||||||
if (string.IsNullOrEmpty(serviceHealth))
|
if (string.IsNullOrEmpty(serviceHealth))
|
||||||
{
|
{
|
||||||
// Container has no HEALTHCHECK
|
// Container has no HEALTHCHECK
|
||||||
return String.Empty;
|
return true;
|
||||||
}
|
}
|
||||||
var retryCount = 0;
|
var retryCount = 0;
|
||||||
while (string.Equals(serviceHealth, "starting", StringComparison.OrdinalIgnoreCase))
|
while (string.Equals(serviceHealth, "starting", StringComparison.OrdinalIgnoreCase))
|
||||||
@@ -441,7 +441,7 @@ namespace GitHub.Runner.Worker
|
|||||||
serviceHealth = (await _dockerManager.DockerInspect(context: executionContext, dockerObject: container.ContainerId, options: healthCheck)).FirstOrDefault();
|
serviceHealth = (await _dockerManager.DockerInspect(context: executionContext, dockerObject: container.ContainerId, options: healthCheck)).FirstOrDefault();
|
||||||
retryCount++;
|
retryCount++;
|
||||||
}
|
}
|
||||||
return serviceHealth;
|
return string.Equals(serviceHealth, "healthy", StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> ContainerRegistryLogin(IExecutionContext executionContext, ContainerInfo container)
|
private async Task<string> ContainerRegistryLogin(IExecutionContext executionContext, ContainerInfo container)
|
||||||
@@ -562,7 +562,7 @@ namespace GitHub.Runner.Worker
|
|||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
Version requiredDockerEngineAPIVersion = new Version(1, 30); // Docker-EE version 17.6
|
Version requiredDockerEngineAPIVersion = new Version(1, 30); // Docker-EE version 17.6
|
||||||
#else
|
#else
|
||||||
Version requiredDockerEngineAPIVersion = new Version(1, 35); // Docker-CE version 17.12
|
Version requiredDockerEngineAPIVersion = new(1, 35); // Docker-CE version 17.12
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (dockerVersion.ServerVersion < requiredDockerEngineAPIVersion)
|
if (dockerVersion.ServerVersion < requiredDockerEngineAPIVersion)
|
||||||
|
|||||||
@@ -122,10 +122,10 @@ namespace GitHub.Runner.Worker
|
|||||||
private const int _maxIssueCountInTelemetry = 3; // Only send the first 3 issues to telemetry
|
private const int _maxIssueCountInTelemetry = 3; // Only send the first 3 issues to telemetry
|
||||||
private const int _maxIssueMessageLengthInTelemetry = 256; // Only send the first 256 characters of issue message to telemetry
|
private const int _maxIssueMessageLengthInTelemetry = 256; // Only send the first 256 characters of issue message to telemetry
|
||||||
|
|
||||||
private readonly TimelineRecord _record = new TimelineRecord();
|
private readonly TimelineRecord _record = new();
|
||||||
private readonly Dictionary<Guid, TimelineRecord> _detailRecords = new Dictionary<Guid, TimelineRecord>();
|
private readonly Dictionary<Guid, TimelineRecord> _detailRecords = new();
|
||||||
private readonly object _loggerLock = new object();
|
private readonly object _loggerLock = new();
|
||||||
private readonly object _matchersLock = new object();
|
private readonly object _matchersLock = new();
|
||||||
|
|
||||||
private event OnMatcherChanged _onMatcherChanged;
|
private event OnMatcherChanged _onMatcherChanged;
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@ namespace GitHub.Runner.Worker
|
|||||||
private bool _expandedForPostJob = false;
|
private bool _expandedForPostJob = false;
|
||||||
private int _childTimelineRecordOrder = 0;
|
private int _childTimelineRecordOrder = 0;
|
||||||
private CancellationTokenSource _cancellationTokenSource;
|
private CancellationTokenSource _cancellationTokenSource;
|
||||||
private TaskCompletionSource<int> _forceCompleted = new TaskCompletionSource<int>();
|
private TaskCompletionSource<int> _forceCompleted = new();
|
||||||
private bool _throttlingReported = false;
|
private bool _throttlingReported = false;
|
||||||
|
|
||||||
// only job level ExecutionContext will track throttling delay.
|
// only job level ExecutionContext will track throttling delay.
|
||||||
@@ -686,8 +686,11 @@ namespace GitHub.Runner.Worker
|
|||||||
// Endpoints
|
// Endpoints
|
||||||
Global.Endpoints = message.Resources.Endpoints;
|
Global.Endpoints = message.Resources.Endpoints;
|
||||||
|
|
||||||
// Variables
|
// Ser debug using vars context if debug variables are not already present.
|
||||||
Global.Variables = new Variables(HostContext, message.Variables);
|
var variables = message.Variables;
|
||||||
|
SetDebugUsingVars(variables, message.ContextData);
|
||||||
|
|
||||||
|
Global.Variables = new Variables(HostContext, variables);
|
||||||
|
|
||||||
if (Global.Variables.GetBoolean("DistributedTask.ForceInternalNodeVersionOnRunnerTo12") ?? false)
|
if (Global.Variables.GetBoolean("DistributedTask.ForceInternalNodeVersionOnRunnerTo12") ?? false)
|
||||||
{
|
{
|
||||||
@@ -1077,6 +1080,31 @@ namespace GitHub.Runner.Worker
|
|||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sets debug using vars context in case debug variables are not present.
|
||||||
|
private static void SetDebugUsingVars(IDictionary<string, VariableValue> variables, IDictionary<string, PipelineContextData> contextData)
|
||||||
|
{
|
||||||
|
if (contextData != null &&
|
||||||
|
contextData.TryGetValue(PipelineTemplateConstants.Vars, out var varsPipelineContextData) &&
|
||||||
|
varsPipelineContextData != null &&
|
||||||
|
varsPipelineContextData is DictionaryContextData varsContextData)
|
||||||
|
{
|
||||||
|
// Set debug variables only when StepDebug/RunnerDebug variables are not present.
|
||||||
|
if (!variables.ContainsKey(Constants.Variables.Actions.StepDebug) &&
|
||||||
|
varsContextData.TryGetValue(Constants.Variables.Actions.StepDebug, out var stepDebugValue) &&
|
||||||
|
stepDebugValue is StringContextData)
|
||||||
|
{
|
||||||
|
variables[Constants.Variables.Actions.StepDebug] = stepDebugValue.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!variables.ContainsKey(Constants.Variables.Actions.RunnerDebug) &&
|
||||||
|
varsContextData.TryGetValue(Constants.Variables.Actions.RunnerDebug, out var runDebugValue) &&
|
||||||
|
runDebugValue is StringContextData)
|
||||||
|
{
|
||||||
|
variables[Constants.Variables.Actions.RunnerDebug] = runDebugValue.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void ApplyContinueOnError(TemplateToken continueOnErrorToken)
|
public void ApplyContinueOnError(TemplateToken continueOnErrorToken)
|
||||||
{
|
{
|
||||||
if (Result != TaskResult.Failed)
|
if (Result != TaskResult.Failed)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ namespace GitHub.Runner.Worker.Expressions
|
|||||||
|
|
||||||
string githubWorkspace = workspaceData.Value;
|
string githubWorkspace = workspaceData.Value;
|
||||||
bool followSymlink = false;
|
bool followSymlink = false;
|
||||||
List<string> patterns = new List<string>();
|
List<string> patterns = new();
|
||||||
var firstParameter = true;
|
var firstParameter = true;
|
||||||
foreach (var parameter in Parameters)
|
foreach (var parameter in Parameters)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
public sealed class GitHubContext : DictionaryContextData, IEnvironmentContextData
|
public sealed class GitHubContext : DictionaryContextData, IEnvironmentContextData
|
||||||
{
|
{
|
||||||
private readonly HashSet<string> _contextEnvAllowlist = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
private readonly HashSet<string> _contextEnvAllowlist = new(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
"action_path",
|
"action_path",
|
||||||
"action_ref",
|
"action_ref",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -7,8 +8,8 @@ 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;
|
using GitHub.Runner.Common;
|
||||||
using GitHub.Runner.Sdk;
|
|
||||||
using GitHub.Runner.Common.Util;
|
using GitHub.Runner.Common.Util;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
using GitHub.Runner.Worker.Container;
|
using GitHub.Runner.Worker.Container;
|
||||||
using GitHub.Runner.Worker.Container.ContainerHooks;
|
using GitHub.Runner.Worker.Container.ContainerHooks;
|
||||||
|
|
||||||
@@ -137,13 +138,25 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
|
|
||||||
if (Data.NodeVersion == "node12" && (ExecutionContext.Global.Variables.GetBoolean(Constants.Runner.Features.Node12Warning) ?? false))
|
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 repoAction = Action as RepositoryPathReference;
|
||||||
var actionDisplayName = new StringContextData(repoAction.Name ?? repoAction.Path); // local actions don't have a 'Name'
|
var warningActions = new HashSet<string>();
|
||||||
ExecutionContext.JobContext["Node12ActionsWarnings"].AssertArray("Node12ActionsWarnings").Add(actionDisplayName);
|
if (ExecutionContext.Global.Variables.TryGetValue("Node12ActionsWarnings", out var node12Warnings))
|
||||||
|
{
|
||||||
|
warningActions = StringUtil.ConvertFromJson<HashSet<string>>(node12Warnings);
|
||||||
|
}
|
||||||
|
|
||||||
|
var repoActionFullName = "";
|
||||||
|
if (string.IsNullOrEmpty(repoAction.Name))
|
||||||
|
{
|
||||||
|
repoActionFullName = repoAction.Path; // local actions don't have a 'Name'
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
repoActionFullName = $"{repoAction.Name}/{repoAction.Path ?? string.Empty}".TrimEnd('/') + $"@{repoAction.Ref}";
|
||||||
|
}
|
||||||
|
|
||||||
|
warningActions.Add(repoActionFullName);
|
||||||
|
ExecutionContext.Global.Variables.Set("Node12ActionsWarnings", StringUtil.ConvertToJson(warningActions));
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager))
|
using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager))
|
||||||
|
|||||||
@@ -16,16 +16,16 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
private const string _colorCodePrefix = "\033[";
|
private const string _colorCodePrefix = "\033[";
|
||||||
private const int _maxAttempts = 3;
|
private const int _maxAttempts = 3;
|
||||||
private const string _timeoutKey = "GITHUB_ACTIONS_RUNNER_ISSUE_MATCHER_TIMEOUT";
|
private const string _timeoutKey = "GITHUB_ACTIONS_RUNNER_ISSUE_MATCHER_TIMEOUT";
|
||||||
private static readonly Regex _colorCodeRegex = new Regex(@"\x0033\[[0-9;]*m?", RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
private static readonly Regex _colorCodeRegex = new(@"\x0033\[[0-9;]*m?", RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||||
private readonly IActionCommandManager _commandManager;
|
private readonly IActionCommandManager _commandManager;
|
||||||
private readonly ContainerInfo _container;
|
private readonly ContainerInfo _container;
|
||||||
private readonly IExecutionContext _executionContext;
|
private readonly IExecutionContext _executionContext;
|
||||||
private readonly int _failsafe = 50;
|
private readonly int _failsafe = 50;
|
||||||
private readonly object _matchersLock = new object();
|
private readonly object _matchersLock = new();
|
||||||
private readonly TimeSpan _timeout;
|
private readonly TimeSpan _timeout;
|
||||||
private IssueMatcher[] _matchers = Array.Empty<IssueMatcher>();
|
private IssueMatcher[] _matchers = Array.Empty<IssueMatcher>();
|
||||||
// Mapping that indicates whether a directory belongs to the workflow repository
|
// Mapping that indicates whether a directory belongs to the workflow repository
|
||||||
private readonly Dictionary<string, string> _directoryMap = new Dictionary<string, string>();
|
private readonly Dictionary<string, string> _directoryMap = new();
|
||||||
|
|
||||||
public OutputManager(IExecutionContext executionContext, IActionCommandManager commandManager, ContainerInfo container = null)
|
public OutputManager(IExecutionContext executionContext, IActionCommandManager commandManager, ContainerInfo container = null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
{
|
{
|
||||||
internal static class ScriptHandlerHelpers
|
internal static class ScriptHandlerHelpers
|
||||||
{
|
{
|
||||||
private static readonly Dictionary<string, string> _defaultArguments = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
private static readonly Dictionary<string, string> _defaultArguments = new(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
["cmd"] = "/D /E:ON /V:OFF /S /C \"CALL \"{0}\"\"",
|
["cmd"] = "/D /E:ON /V:OFF /S /C \"CALL \"{0}\"\"",
|
||||||
["pwsh"] = "-command \". '{0}'\"",
|
["pwsh"] = "-command \". '{0}'\"",
|
||||||
@@ -20,7 +20,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
["python"] = "{0}"
|
["python"] = "{0}"
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly Dictionary<string, string> _extensions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
private static readonly Dictionary<string, string> _extensions = new(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
["cmd"] = ".cmd",
|
["cmd"] = ".cmd",
|
||||||
["pwsh"] = ".ps1",
|
["pwsh"] = ".ps1",
|
||||||
|
|||||||
@@ -39,10 +39,10 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public sealed class JobExtension : RunnerService, IJobExtension
|
public sealed class JobExtension : RunnerService, IJobExtension
|
||||||
{
|
{
|
||||||
private readonly HashSet<string> _existingProcesses = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
private readonly HashSet<string> _existingProcesses = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private bool _processCleanup;
|
private bool _processCleanup;
|
||||||
private string _processLookupId = $"github_{Guid.NewGuid()}";
|
private string _processLookupId = $"github_{Guid.NewGuid()}";
|
||||||
private CancellationTokenSource _diskSpaceCheckToken = new CancellationTokenSource();
|
private CancellationTokenSource _diskSpaceCheckToken = new();
|
||||||
private Task _diskSpaceCheckTask = null;
|
private Task _diskSpaceCheckTask = null;
|
||||||
|
|
||||||
// Download all required actions.
|
// Download all required actions.
|
||||||
@@ -59,8 +59,8 @@ namespace GitHub.Runner.Worker
|
|||||||
context.StepTelemetry.Type = "runner";
|
context.StepTelemetry.Type = "runner";
|
||||||
context.StepTelemetry.Action = "setup_job";
|
context.StepTelemetry.Action = "setup_job";
|
||||||
|
|
||||||
List<IStep> preJobSteps = new List<IStep>();
|
List<IStep> preJobSteps = new();
|
||||||
List<IStep> jobSteps = new List<IStep>();
|
List<IStep> jobSteps = new();
|
||||||
using (var register = jobContext.CancellationToken.Register(() => { context.CancelToken(); }))
|
using (var register = jobContext.CancellationToken.Register(() => { context.CancelToken(); }))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -220,6 +220,11 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
var networkAlias = pair.Key;
|
var networkAlias = pair.Key;
|
||||||
var serviceContainer = pair.Value;
|
var serviceContainer = pair.Value;
|
||||||
|
if (serviceContainer == null)
|
||||||
|
{
|
||||||
|
context.Output($"The service '{networkAlias}' will not be started because the container definition has an empty image.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
jobContext.Global.ServiceContainers.Add(new Container.ContainerInfo(HostContext, serviceContainer, false, networkAlias));
|
jobContext.Global.ServiceContainers.Add(new Container.ContainerInfo(HostContext, serviceContainer, false, networkAlias));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -386,7 +391,7 @@ namespace GitHub.Runner.Worker
|
|||||||
data: (object)jobHookData));
|
data: (object)jobHookData));
|
||||||
}
|
}
|
||||||
|
|
||||||
List<IStep> steps = new List<IStep>();
|
List<IStep> steps = new();
|
||||||
steps.AddRange(preJobSteps);
|
steps.AddRange(preJobSteps);
|
||||||
steps.AddRange(jobSteps);
|
steps.AddRange(jobSteps);
|
||||||
|
|
||||||
@@ -674,7 +679,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
private Dictionary<int, Process> SnapshotProcesses()
|
private Dictionary<int, Process> SnapshotProcesses()
|
||||||
{
|
{
|
||||||
Dictionary<int, Process> snapshot = new Dictionary<int, Process>();
|
Dictionary<int, Process> snapshot = new();
|
||||||
foreach (var proc in Process.GetProcesses())
|
foreach (var proc in Process.GetProcesses())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -258,9 +258,9 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jobContext.JobContext.ContainsKey("Node12ActionsWarnings"))
|
if (jobContext.Global.Variables.TryGetValue("Node12ActionsWarnings", out var node12Warnings))
|
||||||
{
|
{
|
||||||
var actions = string.Join(", ", jobContext.JobContext["Node12ActionsWarnings"].AssertArray("Node12ActionsWarnings").Select(action => action.ToString()));
|
var actions = string.Join(", ", StringUtil.ConvertFromJson<HashSet<string>>(node12Warnings));
|
||||||
jobContext.Warning(string.Format(Constants.Runner.Node12DetectedAfterEndOfLife, actions));
|
jobContext.Warning(string.Format(Constants.Runner.Node12DetectedAfterEndOfLife, actions));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
public static int Main(string[] args)
|
public static int Main(string[] args)
|
||||||
{
|
{
|
||||||
using (HostContext context = new HostContext("Worker"))
|
using (HostContext context = new("Worker"))
|
||||||
{
|
{
|
||||||
return MainAsync(context, args).GetAwaiter().GetResult();
|
return MainAsync(context, args).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public sealed class RunnerPluginManager : RunnerService, IRunnerPluginManager
|
public sealed class RunnerPluginManager : RunnerService, IRunnerPluginManager
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, RunnerPluginActionInfo> _actionPlugins = new Dictionary<string, RunnerPluginActionInfo>(StringComparer.OrdinalIgnoreCase)
|
private readonly Dictionary<string, RunnerPluginActionInfo> _actionPlugins = new(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
"checkout",
|
"checkout",
|
||||||
@@ -97,7 +97,7 @@ namespace GitHub.Runner.Worker
|
|||||||
string arguments = $"action \"{plugin}\"";
|
string arguments = $"action \"{plugin}\"";
|
||||||
|
|
||||||
// construct plugin context
|
// construct plugin context
|
||||||
RunnerActionPluginExecutionContext pluginContext = new RunnerActionPluginExecutionContext
|
RunnerActionPluginExecutionContext pluginContext = new()
|
||||||
{
|
{
|
||||||
Inputs = inputs,
|
Inputs = inputs,
|
||||||
Endpoints = context.Global.Endpoints,
|
Endpoints = context.Global.Endpoints,
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ namespace GitHub.Runner.Worker
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class StepsContext
|
public sealed class StepsContext
|
||||||
{
|
{
|
||||||
private static readonly Regex _propertyRegex = new Regex("^[a-zA-Z_][a-zA-Z0-9_]*$", RegexOptions.Compiled);
|
private static readonly Regex _propertyRegex = new("^[a-zA-Z_][a-zA-Z0-9_]*$", RegexOptions.Compiled);
|
||||||
private readonly DictionaryContextData _contextData = new DictionaryContextData();
|
private readonly DictionaryContextData _contextData = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clears memory for a composite action's isolated "steps" context, after the action
|
/// Clears memory for a composite action's isolated "steps" context, after the action
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ namespace GitHub.Runner.Worker
|
|||||||
Trace.Entering();
|
Trace.Entering();
|
||||||
|
|
||||||
// Create the new tracking config.
|
// Create the new tracking config.
|
||||||
TrackingConfig config = new TrackingConfig(executionContext);
|
TrackingConfig config = new(executionContext);
|
||||||
WriteToFile(file, config);
|
WriteToFile(file, config);
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
using GitHub.DistributedTask.Logging;
|
using GitHub.DistributedTask.Logging;
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
|
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;
|
||||||
@@ -14,9 +14,9 @@ namespace GitHub.Runner.Worker
|
|||||||
public sealed class Variables
|
public sealed class Variables
|
||||||
{
|
{
|
||||||
private readonly IHostContext _hostContext;
|
private readonly IHostContext _hostContext;
|
||||||
private readonly ConcurrentDictionary<string, Variable> _variables = new ConcurrentDictionary<string, Variable>(StringComparer.OrdinalIgnoreCase);
|
private readonly ConcurrentDictionary<string, Variable> _variables = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private readonly ISecretMasker _secretMasker;
|
private readonly ISecretMasker _secretMasker;
|
||||||
private readonly object _setLock = new object();
|
private readonly object _setLock = new();
|
||||||
private readonly Tracing _trace;
|
private readonly Tracing _trace;
|
||||||
|
|
||||||
public IEnumerable<Variable> AllVariables
|
public IEnumerable<Variable> AllVariables
|
||||||
@@ -43,7 +43,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the variable dictionary.
|
// Initialize the variable dictionary.
|
||||||
List<Variable> variables = new List<Variable>();
|
List<Variable> variables = new();
|
||||||
foreach (var variable in copy)
|
foreach (var variable in copy)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(variable.Key))
|
if (!string.IsNullOrWhiteSpace(variable.Key))
|
||||||
@@ -136,6 +136,12 @@ namespace GitHub.Runner.Worker
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Set(string name, string val)
|
||||||
|
{
|
||||||
|
ArgUtil.NotNullOrEmpty(name, nameof(name));
|
||||||
|
_variables[name] = new Variable(name, val, false);
|
||||||
|
}
|
||||||
|
|
||||||
public bool TryGetValue(string name, out string val)
|
public bool TryGetValue(string name, out string val)
|
||||||
{
|
{
|
||||||
Variable variable;
|
Variable variable;
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ namespace GitHub.Runner.Worker
|
|||||||
public sealed class Worker : RunnerService, IWorker
|
public sealed class Worker : RunnerService, IWorker
|
||||||
{
|
{
|
||||||
private readonly TimeSpan _workerStartTimeout = TimeSpan.FromSeconds(30);
|
private readonly TimeSpan _workerStartTimeout = TimeSpan.FromSeconds(30);
|
||||||
private ManualResetEvent _completedCommand = new ManualResetEvent(false);
|
private ManualResetEvent _completedCommand = new(false);
|
||||||
|
|
||||||
// Do not mask the values of these secrets
|
// Do not mask the values of these secrets
|
||||||
private static HashSet<String> SecretVariableMaskWhitelist = new HashSet<String>(StringComparer.OrdinalIgnoreCase)
|
private static HashSet<String> SecretVariableMaskWhitelist = new(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
Constants.Variables.Actions.StepDebug,
|
Constants.Variables.Actions.StepDebug,
|
||||||
Constants.Variables.Actions.RunnerDebug
|
Constants.Variables.Actions.RunnerDebug
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using GitHub.Services.OAuth;
|
||||||
|
|
||||||
|
namespace GitHub.Services.Common
|
||||||
|
{
|
||||||
|
public static class VssCredentialsExtension
|
||||||
|
{
|
||||||
|
public static VssOAuthCredential ToOAuthCredentials(
|
||||||
|
this VssCredentials credentials)
|
||||||
|
{
|
||||||
|
if (credentials.Federated.CredentialType == VssCredentialsType.OAuth)
|
||||||
|
{
|
||||||
|
return credentials.Federated as VssOAuthCredential;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
194
src/Sdk/Common/Common/RawClientHttpRequestSettings.cs
Normal file
194
src/Sdk/Common/Common/RawClientHttpRequestSettings.cs
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Net.Security;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using GitHub.Services.WebApi.Utilities.Internal;
|
||||||
|
|
||||||
|
namespace GitHub.Services.Common
|
||||||
|
{
|
||||||
|
public class RawClientHttpRequestSettings
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Timespan to wait before timing out a request. Defaults to 100 seconds
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan SendTimeout
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// User-Agent header passed along in the request,
|
||||||
|
/// For multiple values, the order in the list is the order
|
||||||
|
/// in which they will appear in the header
|
||||||
|
/// </summary>
|
||||||
|
public List<ProductInfoHeaderValue> UserAgent
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the culture is passed in the Accept-Language header
|
||||||
|
/// </summary>
|
||||||
|
public ICollection<CultureInfo> AcceptLanguages
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return m_acceptLanguages;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A unique identifier for the user session
|
||||||
|
/// </summary>
|
||||||
|
public Guid SessionId
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional implementation used to validate server certificate validation
|
||||||
|
/// </summary>
|
||||||
|
public Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool> ServerCertificateValidationCallback
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of times to retry a request that has an ambient failure
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This property is only used by RawConnection, so only relevant on the client
|
||||||
|
/// </remarks>
|
||||||
|
[DefaultValue(c_defaultMaxRetry)]
|
||||||
|
public Int32 MaxRetryRequest
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the property name used to reference this object.
|
||||||
|
/// </summary>
|
||||||
|
public const String PropertyName = "Actions.RequestSettings";
|
||||||
|
|
||||||
|
public static RawClientHttpRequestSettings Default => s_defaultSettings.Value;
|
||||||
|
|
||||||
|
protected RawClientHttpRequestSettings(RawClientHttpRequestSettings copy)
|
||||||
|
{
|
||||||
|
this.SendTimeout = copy.SendTimeout;
|
||||||
|
this.m_acceptLanguages = new List<CultureInfo>(copy.AcceptLanguages);
|
||||||
|
this.SessionId = copy.SessionId;
|
||||||
|
this.UserAgent = new List<ProductInfoHeaderValue>(copy.UserAgent);
|
||||||
|
this.ServerCertificateValidationCallback = copy.ServerCertificateValidationCallback;
|
||||||
|
this.MaxRetryRequest = copy.MaxRetryRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RawClientHttpRequestSettings Clone()
|
||||||
|
{
|
||||||
|
return new RawClientHttpRequestSettings(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RawClientHttpRequestSettings()
|
||||||
|
: this(Guid.NewGuid())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public RawClientHttpRequestSettings(Guid sessionId)
|
||||||
|
{
|
||||||
|
this.SendTimeout = s_defaultTimeout;
|
||||||
|
if (!String.IsNullOrEmpty(CultureInfo.CurrentUICulture.Name)) // InvariantCulture for example has an empty name.
|
||||||
|
{
|
||||||
|
this.AcceptLanguages.Add(CultureInfo.CurrentUICulture);
|
||||||
|
}
|
||||||
|
this.SessionId = sessionId;
|
||||||
|
this.ServerCertificateValidationCallback = null;
|
||||||
|
|
||||||
|
// If different, we'll also add CurrentCulture to the request headers,
|
||||||
|
// but UICulture was added first, so it gets first preference
|
||||||
|
if (!CultureInfo.CurrentCulture.Equals(CultureInfo.CurrentUICulture) && !String.IsNullOrEmpty(CultureInfo.CurrentCulture.Name))
|
||||||
|
{
|
||||||
|
this.AcceptLanguages.Add(CultureInfo.CurrentCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.MaxRetryRequest = c_defaultMaxRetry;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
string customClientRequestTimeout = Environment.GetEnvironmentVariable("VSS_Client_Request_Timeout");
|
||||||
|
if (!string.IsNullOrEmpty(customClientRequestTimeout) && int.TryParse(customClientRequestTimeout, out int customTimeout))
|
||||||
|
{
|
||||||
|
// avoid disrupting a debug session due to the request timing out by setting a custom timeout.
|
||||||
|
this.SendTimeout = TimeSpan.FromSeconds(customTimeout);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal virtual Boolean ApplyTo(HttpRequestMessage request)
|
||||||
|
{
|
||||||
|
// Make sure we only apply the settings to the request once
|
||||||
|
if (request.Options.TryGetValue<object>(PropertyName, out _))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
request.Options.Set(new HttpRequestOptionsKey<RawClientHttpRequestSettings>(PropertyName), this);
|
||||||
|
|
||||||
|
if (this.AcceptLanguages != null && this.AcceptLanguages.Count > 0)
|
||||||
|
{
|
||||||
|
// An empty or null CultureInfo name will cause an ArgumentNullException in the
|
||||||
|
// StringWithQualityHeaderValue constructor. CultureInfo.InvariantCulture is an example of
|
||||||
|
// a CultureInfo that has an empty name.
|
||||||
|
foreach (CultureInfo culture in this.AcceptLanguages.Where(a => !String.IsNullOrEmpty(a.Name)))
|
||||||
|
{
|
||||||
|
request.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue(culture.Name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.UserAgent != null)
|
||||||
|
{
|
||||||
|
foreach (var headerVal in this.UserAgent)
|
||||||
|
{
|
||||||
|
if (!request.Headers.UserAgent.Contains(headerVal))
|
||||||
|
{
|
||||||
|
request.Headers.UserAgent.Add(headerVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!request.Headers.Contains(Internal.RawHttpHeaders.SessionHeader))
|
||||||
|
{
|
||||||
|
request.Headers.Add(Internal.RawHttpHeaders.SessionHeader, this.SessionId.ToString("D"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an instance of the default request settings.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The default request settings</returns>
|
||||||
|
private static RawClientHttpRequestSettings ConstructDefaultSettings()
|
||||||
|
{
|
||||||
|
// Set up reasonable defaults in case the registry keys are not present
|
||||||
|
var settings = new RawClientHttpRequestSettings();
|
||||||
|
settings.UserAgent = UserAgentUtility.GetDefaultRestUserAgent();
|
||||||
|
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Lazy<RawClientHttpRequestSettings> s_defaultSettings
|
||||||
|
= new Lazy<RawClientHttpRequestSettings>(ConstructDefaultSettings);
|
||||||
|
|
||||||
|
private const Int32 c_defaultMaxRetry = 3;
|
||||||
|
private static readonly TimeSpan s_defaultTimeout = TimeSpan.FromSeconds(100); //default WebAPI timeout
|
||||||
|
private ICollection<CultureInfo> m_acceptLanguages = new List<CultureInfo>();
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/Sdk/Common/Common/RawHttpHeaders.cs
Normal file
12
src/Sdk/Common/Common/RawHttpHeaders.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace GitHub.Services.Common.Internal
|
||||||
|
{
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public static class RawHttpHeaders
|
||||||
|
{
|
||||||
|
public const String SessionHeader = "X-Runner-Session";
|
||||||
|
}
|
||||||
|
}
|
||||||
349
src/Sdk/Common/Common/RawHttpMessageHandler.cs
Normal file
349
src/Sdk/Common/Common/RawHttpMessageHandler.cs
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GitHub.Services.Common.Diagnostics;
|
||||||
|
using GitHub.Services.Common.Internal;
|
||||||
|
using GitHub.Services.OAuth;
|
||||||
|
|
||||||
|
namespace GitHub.Services.Common
|
||||||
|
{
|
||||||
|
public class RawHttpMessageHandler: HttpMessageHandler
|
||||||
|
{
|
||||||
|
public RawHttpMessageHandler(
|
||||||
|
VssOAuthCredential credentials)
|
||||||
|
: this(credentials, new RawClientHttpRequestSettings())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public RawHttpMessageHandler(
|
||||||
|
VssOAuthCredential credentials,
|
||||||
|
RawClientHttpRequestSettings settings)
|
||||||
|
: this(credentials, settings, new HttpClientHandler())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public RawHttpMessageHandler(
|
||||||
|
VssOAuthCredential credentials,
|
||||||
|
RawClientHttpRequestSettings settings,
|
||||||
|
HttpMessageHandler innerHandler)
|
||||||
|
{
|
||||||
|
this.Credentials = credentials;
|
||||||
|
this.Settings = settings;
|
||||||
|
m_messageInvoker = new HttpMessageInvoker(innerHandler);
|
||||||
|
m_credentialWrapper = new CredentialWrapper();
|
||||||
|
|
||||||
|
// If we were given a pipeline make sure we find the inner-most handler to apply our settings as this
|
||||||
|
// will be the actual outgoing transport.
|
||||||
|
{
|
||||||
|
HttpMessageHandler transportHandler = innerHandler;
|
||||||
|
DelegatingHandler delegatingHandler = transportHandler as DelegatingHandler;
|
||||||
|
while (delegatingHandler != null)
|
||||||
|
{
|
||||||
|
transportHandler = delegatingHandler.InnerHandler;
|
||||||
|
delegatingHandler = transportHandler as DelegatingHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_transportHandler = transportHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplySettings(m_transportHandler, m_credentialWrapper, this.Settings);
|
||||||
|
|
||||||
|
m_thisLock = new Object();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the credentials associated with this handler.
|
||||||
|
/// </summary>
|
||||||
|
public VssOAuthCredential Credentials
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the settings associated with this handler.
|
||||||
|
/// </summary>
|
||||||
|
public RawClientHttpRequestSettings Settings
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
// setting this to WebRequest.DefaultWebProxy in NETSTANDARD is causing a System.PlatformNotSupportedException
|
||||||
|
//.in System.Net.SystemWebProxy.IsBypassed. Comment in IsBypassed method indicates ".NET Core and .NET Native
|
||||||
|
// code will handle this exception and call into WinInet/WinHttp as appropriate to use the system proxy."
|
||||||
|
// This needs to be investigated further.
|
||||||
|
private static IWebProxy s_defaultWebProxy = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows you to set a proxy to be used by all RawHttpMessageHandler requests without affecting the global WebRequest.DefaultWebProxy. If not set it returns the WebRequest.DefaultWebProxy.
|
||||||
|
/// </summary>
|
||||||
|
public static IWebProxy DefaultWebProxy
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var toReturn = WebProxyWrapper.Wrap(s_defaultWebProxy);
|
||||||
|
|
||||||
|
if (null != toReturn &&
|
||||||
|
toReturn.Credentials == null)
|
||||||
|
{
|
||||||
|
toReturn.Credentials = CredentialCache.DefaultCredentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
s_defaultWebProxy = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task<HttpResponseMessage> SendAsync(
|
||||||
|
HttpRequestMessage request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
VssTraceActivity traceActivity = VssTraceActivity.Current;
|
||||||
|
|
||||||
|
lock (m_thisLock)
|
||||||
|
{
|
||||||
|
// Ensure that we attempt to use the most appropriate authentication mechanism by default.
|
||||||
|
if (m_tokenProvider == null)
|
||||||
|
{
|
||||||
|
m_tokenProvider = this.Credentials.GetTokenProvider(request.RequestUri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CancellationTokenSource tokenSource = null;
|
||||||
|
HttpResponseMessage response = null;
|
||||||
|
Boolean succeeded = false;
|
||||||
|
HttpResponseMessageWrapper responseWrapper;
|
||||||
|
|
||||||
|
Int32 retries = m_maxAuthRetries;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||||
|
if (this.Settings.SendTimeout > TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
tokenSource.CancelAfter(this.Settings.SendTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (response != null)
|
||||||
|
{
|
||||||
|
response.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's start with sending a token
|
||||||
|
IssuedToken token = await m_tokenProvider.GetTokenAsync(null, tokenSource.Token).ConfigureAwait(false);
|
||||||
|
ApplyToken(request, token);
|
||||||
|
|
||||||
|
// ConfigureAwait(false) enables the continuation to be run outside any captured
|
||||||
|
// SyncronizationContext (such as ASP.NET's) which keeps things from deadlocking...
|
||||||
|
response = await m_messageInvoker.SendAsync(request, tokenSource.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
responseWrapper = new HttpResponseMessageWrapper(response);
|
||||||
|
|
||||||
|
var isUnAuthorized = responseWrapper.StatusCode == HttpStatusCode.Unauthorized;
|
||||||
|
if (!isUnAuthorized)
|
||||||
|
{
|
||||||
|
// Validate the token after it has been successfully authenticated with the server.
|
||||||
|
m_tokenProvider?.ValidateToken(token, responseWrapper);
|
||||||
|
succeeded = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_tokenProvider?.InvalidateToken(token);
|
||||||
|
|
||||||
|
if (retries == 0 || retries < m_maxAuthRetries)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
token = await m_tokenProvider.GetTokenAsync(token, tokenSource.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
retries--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (retries >= 0);
|
||||||
|
|
||||||
|
// We're out of retries and the response was an auth challenge -- then the request was unauthorized
|
||||||
|
// and we will throw a strongly-typed exception with a friendly error message.
|
||||||
|
if (!succeeded && response != null && responseWrapper.StatusCode == HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
// Make sure we do not leak the response object when raising an exception
|
||||||
|
if (response != null)
|
||||||
|
{
|
||||||
|
response.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = CommonResources.VssUnauthorized(request.RequestUri.GetLeftPart(UriPartial.Authority));
|
||||||
|
VssHttpEventSource.Log.HttpRequestUnauthorized(traceActivity, request, message);
|
||||||
|
VssUnauthorizedException unauthorizedException = new VssUnauthorizedException(message);
|
||||||
|
throw unauthorizedException;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException ex)
|
||||||
|
{
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
VssHttpEventSource.Log.HttpRequestCancelled(traceActivity, request);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
VssHttpEventSource.Log.HttpRequestTimedOut(traceActivity, request, this.Settings.SendTimeout);
|
||||||
|
throw new TimeoutException(CommonResources.HttpRequestTimeout(this.Settings.SendTimeout), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// We always dispose of the token source since otherwise we leak resources if there is a timer pending
|
||||||
|
if (tokenSource != null)
|
||||||
|
{
|
||||||
|
tokenSource.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyToken(
|
||||||
|
HttpRequestMessage request,
|
||||||
|
IssuedToken token)
|
||||||
|
{
|
||||||
|
switch (token)
|
||||||
|
{
|
||||||
|
case null:
|
||||||
|
return;
|
||||||
|
case ICredentials credentialsToken:
|
||||||
|
m_credentialWrapper.InnerCredentials = credentialsToken;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
token.ApplyTo(new HttpRequestMessageWrapper(request));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplySettings(
|
||||||
|
HttpMessageHandler handler,
|
||||||
|
ICredentials defaultCredentials,
|
||||||
|
RawClientHttpRequestSettings settings)
|
||||||
|
{
|
||||||
|
HttpClientHandler httpClientHandler = handler as HttpClientHandler;
|
||||||
|
if (httpClientHandler != null)
|
||||||
|
{
|
||||||
|
httpClientHandler.ClientCertificateOptions = ClientCertificateOption.Manual;
|
||||||
|
//Setting httpClientHandler.UseDefaultCredentials to false in .Net Core, clears httpClientHandler.Credentials if
|
||||||
|
//credentials is already set to defaultcredentials. Therefore httpClientHandler.Credentials must be
|
||||||
|
//set after httpClientHandler.UseDefaultCredentials.
|
||||||
|
httpClientHandler.UseDefaultCredentials = false;
|
||||||
|
httpClientHandler.Credentials = defaultCredentials;
|
||||||
|
httpClientHandler.PreAuthenticate = false;
|
||||||
|
httpClientHandler.Proxy = DefaultWebProxy;
|
||||||
|
httpClientHandler.UseCookies = false;
|
||||||
|
httpClientHandler.UseProxy = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly HttpMessageHandler m_transportHandler;
|
||||||
|
private HttpMessageInvoker m_messageInvoker;
|
||||||
|
private CredentialWrapper m_credentialWrapper;
|
||||||
|
private object m_thisLock;
|
||||||
|
private const Int32 m_maxAuthRetries = 3;
|
||||||
|
private VssOAuthTokenProvider m_tokenProvider;
|
||||||
|
|
||||||
|
//.Net Core does not attempt NTLM schema on Linux, unless ICredentials is a CredentialCache instance
|
||||||
|
//This workaround may not be needed after this corefx fix is consumed: https://github.com/dotnet/corefx/pull/7923
|
||||||
|
private sealed class CredentialWrapper : CredentialCache, ICredentials
|
||||||
|
{
|
||||||
|
public ICredentials InnerCredentials
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkCredential ICredentials.GetCredential(
|
||||||
|
Uri uri,
|
||||||
|
String authType)
|
||||||
|
{
|
||||||
|
return InnerCredentials != null ? InnerCredentials.GetCredential(uri, authType) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class WebProxyWrapper : IWebProxy
|
||||||
|
{
|
||||||
|
private WebProxyWrapper(IWebProxy toWrap)
|
||||||
|
{
|
||||||
|
m_wrapped = toWrap;
|
||||||
|
m_credentials = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WebProxyWrapper Wrap(IWebProxy toWrap)
|
||||||
|
{
|
||||||
|
if (null == toWrap)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new WebProxyWrapper(toWrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICredentials Credentials
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
ICredentials credentials = m_credentials;
|
||||||
|
|
||||||
|
if (null == credentials)
|
||||||
|
{
|
||||||
|
// This means to fall back to the Credentials from the wrapped
|
||||||
|
// IWebProxy.
|
||||||
|
credentials = m_wrapped.Credentials;
|
||||||
|
}
|
||||||
|
else if (Object.ReferenceEquals(credentials, m_nullCredentials))
|
||||||
|
{
|
||||||
|
// This sentinel value means we have explicitly had our credentials
|
||||||
|
// set to null.
|
||||||
|
credentials = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (null == value)
|
||||||
|
{
|
||||||
|
// Use this as a sentinel value to distinguish the case when someone has
|
||||||
|
// explicitly set our credentials to null. We don't want to fall back to
|
||||||
|
// m_wrapped.Credentials when we have credentials that are explicitly null.
|
||||||
|
m_credentials = m_nullCredentials;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_credentials = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri GetProxy(Uri destination)
|
||||||
|
{
|
||||||
|
return m_wrapped.GetProxy(destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsBypassed(Uri host)
|
||||||
|
{
|
||||||
|
return m_wrapped.IsBypassed(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly IWebProxy m_wrapped;
|
||||||
|
private ICredentials m_credentials;
|
||||||
|
|
||||||
|
private static readonly ICredentials m_nullCredentials = new CredentialWrapper();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -316,7 +316,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
|
|
||||||
if (String.IsNullOrEmpty(result.Image))
|
if (String.IsNullOrEmpty(result.Image))
|
||||||
{
|
{
|
||||||
context.Error(value, "Container image cannot be empty");
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -381,7 +381,7 @@
|
|||||||
"container-mapping": {
|
"container-mapping": {
|
||||||
"mapping": {
|
"mapping": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"image": "non-empty-string",
|
"image": "string",
|
||||||
"options": "non-empty-string",
|
"options": "non-empty-string",
|
||||||
"env": "container-env",
|
"env": "container-env",
|
||||||
"ports": "sequence-of-non-empty-string",
|
"ports": "sequence-of-non-empty-string",
|
||||||
@@ -414,7 +414,7 @@
|
|||||||
"vars"
|
"vars"
|
||||||
],
|
],
|
||||||
"one-of": [
|
"one-of": [
|
||||||
"non-empty-string",
|
"string",
|
||||||
"container-mapping"
|
"container-mapping"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
75
src/Sdk/DTWebApi/WebApi/RunServiceHttpClient.cs
Normal file
75
src/Sdk/DTWebApi/WebApi/RunServiceHttpClient.cs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GitHub.Services.Common;
|
||||||
|
using GitHub.Services.OAuth;
|
||||||
|
using GitHub.Services.WebApi;
|
||||||
|
using Sdk.WebApi.WebApi;
|
||||||
|
|
||||||
|
namespace GitHub.DistributedTask.WebApi
|
||||||
|
{
|
||||||
|
[ResourceArea(TaskResourceIds.AreaId)]
|
||||||
|
public class RunServiceHttpClient : RawHttpClientBase
|
||||||
|
{
|
||||||
|
public RunServiceHttpClient(
|
||||||
|
Uri baseUrl,
|
||||||
|
VssOAuthCredential credentials)
|
||||||
|
: base(baseUrl, credentials)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public RunServiceHttpClient(
|
||||||
|
Uri baseUrl,
|
||||||
|
VssOAuthCredential credentials,
|
||||||
|
RawClientHttpRequestSettings settings)
|
||||||
|
: base(baseUrl, credentials, settings)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public RunServiceHttpClient(
|
||||||
|
Uri baseUrl,
|
||||||
|
VssOAuthCredential credentials,
|
||||||
|
params DelegatingHandler[] handlers)
|
||||||
|
: base(baseUrl, credentials, handlers)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public RunServiceHttpClient(
|
||||||
|
Uri baseUrl,
|
||||||
|
VssOAuthCredential credentials,
|
||||||
|
RawClientHttpRequestSettings settings,
|
||||||
|
params DelegatingHandler[] handlers)
|
||||||
|
: base(baseUrl, credentials, settings, handlers)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public RunServiceHttpClient(
|
||||||
|
Uri baseUrl,
|
||||||
|
HttpMessageHandler pipeline,
|
||||||
|
Boolean disposeHandler)
|
||||||
|
: base(baseUrl, pipeline, disposeHandler)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Pipelines.AgentJobRequestMessage> GetJobMessageAsync(
|
||||||
|
Uri requestUri,
|
||||||
|
string messageId,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
HttpMethod httpMethod = new HttpMethod("POST");
|
||||||
|
var payload = new {
|
||||||
|
StreamID = messageId
|
||||||
|
};
|
||||||
|
|
||||||
|
var payloadJson = JsonUtility.ToString(payload);
|
||||||
|
var requestContent = new StringContent(payloadJson, System.Text.Encoding.UTF8, "application/json");
|
||||||
|
return SendAsync<Pipelines.AgentJobRequestMessage>(
|
||||||
|
httpMethod,
|
||||||
|
additionalHeaders: null,
|
||||||
|
requestUri: requestUri,
|
||||||
|
content: requestContent,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ namespace GitHub.Services.OAuth
|
|||||||
public class VssOAuthCredential : FederatedCredential
|
public class VssOAuthCredential : FederatedCredential
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new <c>VssOAuthCredential</c> instance with the specified authorization grant and client
|
/// Initializes a new <c>VssOAuthCredential</c> instance with the specified authorization grant and client
|
||||||
/// credentials.
|
/// credentials.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="authorizationUrl">The location of the token endpoint for the target authorization server</param>
|
/// <param name="authorizationUrl">The location of the token endpoint for the target authorization server</param>
|
||||||
@@ -117,8 +117,14 @@ namespace GitHub.Services.OAuth
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public VssOAuthTokenProvider GetTokenProvider(
|
||||||
|
Uri serviceUrl)
|
||||||
|
{
|
||||||
|
return new VssOAuthTokenProvider(this, serviceUrl);
|
||||||
|
}
|
||||||
|
|
||||||
protected override IssuedTokenProvider OnCreateTokenProvider(
|
protected override IssuedTokenProvider OnCreateTokenProvider(
|
||||||
Uri serverUrl,
|
Uri serverUrl,
|
||||||
IHttpResponse response)
|
IHttpResponse response)
|
||||||
{
|
{
|
||||||
return new VssOAuthTokenProvider(this, serverUrl);
|
return new VssOAuthTokenProvider(this, serverUrl);
|
||||||
|
|||||||
207
src/Sdk/WebApi/WebApi/RawConnection.cs
Normal file
207
src/Sdk/WebApi/WebApi/RawConnection.cs
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GitHub.Services.Common;
|
||||||
|
using GitHub.Services.OAuth;
|
||||||
|
using GitHub.Services.WebApi;
|
||||||
|
using GitHub.Services.WebApi.Utilities;
|
||||||
|
|
||||||
|
namespace Sdk.WebApi.WebApi.RawClient
|
||||||
|
{
|
||||||
|
public class RawConnection : IDisposable
|
||||||
|
{
|
||||||
|
public RawConnection(
|
||||||
|
Uri baseUrl,
|
||||||
|
VssOAuthCredential credentials,
|
||||||
|
RawClientHttpRequestSettings settings)
|
||||||
|
: this(baseUrl, new RawHttpMessageHandler(credentials, settings), null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public RawConnection(
|
||||||
|
Uri baseUrl,
|
||||||
|
RawHttpMessageHandler innerHandler,
|
||||||
|
IEnumerable<DelegatingHandler> delegatingHandlers)
|
||||||
|
{
|
||||||
|
ArgumentUtility.CheckForNull(baseUrl, "baseUrl");
|
||||||
|
ArgumentUtility.CheckForNull(innerHandler, "innerHandler");
|
||||||
|
|
||||||
|
// Permit delegatingHandlers to be null
|
||||||
|
m_delegatingHandlers = delegatingHandlers = delegatingHandlers ?? Enumerable.Empty<DelegatingHandler>();
|
||||||
|
|
||||||
|
m_baseUrl = baseUrl;
|
||||||
|
m_innerHandler = innerHandler;
|
||||||
|
|
||||||
|
if (this.Settings.MaxRetryRequest > 0)
|
||||||
|
{
|
||||||
|
delegatingHandlers = delegatingHandlers.Concat(new DelegatingHandler[] { new VssHttpRetryMessageHandler(this.Settings.MaxRetryRequest) });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and persist the pipeline.
|
||||||
|
if (delegatingHandlers.Any())
|
||||||
|
{
|
||||||
|
m_pipeline = HttpClientFactory.CreatePipeline(m_innerHandler, delegatingHandlers);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pipeline = m_innerHandler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public RawClientHttpRequestSettings Settings
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (RawClientHttpRequestSettings)m_innerHandler.Settings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<T> GetClientAsync<T>(CancellationToken cancellationToken = default(CancellationToken)) where T : RawHttpClientBase
|
||||||
|
{
|
||||||
|
CheckForDisposed();
|
||||||
|
Type clientType = typeof(T);
|
||||||
|
|
||||||
|
return (T)await GetClientServiceImplAsync(typeof(T), cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Object> GetClientServiceImplAsync(
|
||||||
|
Type requestedType,
|
||||||
|
CancellationToken cancellationToken = default(CancellationToken))
|
||||||
|
{
|
||||||
|
CheckForDisposed();
|
||||||
|
Object requestedObject = null;
|
||||||
|
|
||||||
|
// Get the actual type to lookup or instantiate, which will either be requestedType itself
|
||||||
|
// or an extensible type if one was registered
|
||||||
|
Type managedType = GetExtensibleType(requestedType);
|
||||||
|
|
||||||
|
if (!m_cachedTypes.TryGetValue(managedType, out requestedObject))
|
||||||
|
{
|
||||||
|
AsyncLock typeLock = m_loadingTypes.GetOrAdd(managedType, (t) => new AsyncLock());
|
||||||
|
|
||||||
|
// This ensures only a single thread at a time will be performing the work to initialize this particular type
|
||||||
|
// The other threads will go async awaiting the lock task. This is still an improvement over the old synchronous locking,
|
||||||
|
// as this thread won't be blocked (like a Monitor.Enter), but can return a task to the caller so that the thread
|
||||||
|
// can continue to be used to do useful work while the result is being worked on.
|
||||||
|
// We are trusting that getInstanceAsync does not have any code paths that lead back here (for the same type), otherwise we can deadlock on ourselves.
|
||||||
|
// The old code also extended the same trust which (if violated) would've resulted in a StackOverflowException,
|
||||||
|
// but with async tasks it will lead to a deadlock.
|
||||||
|
using (await typeLock.LockAsync(cancellationToken).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
if (!m_cachedTypes.TryGetValue(managedType, out requestedObject))
|
||||||
|
{
|
||||||
|
requestedObject = (RawHttpClientBase)Activator.CreateInstance(managedType, m_baseUrl, m_pipeline, false /* disposeHandler */);
|
||||||
|
m_cachedTypes[managedType] = requestedObject;
|
||||||
|
|
||||||
|
AsyncLock removed;
|
||||||
|
m_loadingTypes.TryRemove(managedType, out removed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestedObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="managedType"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private Type GetExtensibleType(Type managedType)
|
||||||
|
{
|
||||||
|
if (managedType.GetTypeInfo().IsAbstract || managedType.GetTypeInfo().IsInterface)
|
||||||
|
{
|
||||||
|
Type extensibleType = null;
|
||||||
|
|
||||||
|
// We can add extensible type registration for the client later (app.config? windows registry?). For now it is based solely on the attribute
|
||||||
|
if (!m_extensibleServiceTypes.TryGetValue(managedType.Name, out extensibleType))
|
||||||
|
{
|
||||||
|
VssClientServiceImplementationAttribute[] attributes = (VssClientServiceImplementationAttribute[])managedType.GetTypeInfo().GetCustomAttributes<VssClientServiceImplementationAttribute>(true);
|
||||||
|
if (attributes.Length > 0)
|
||||||
|
{
|
||||||
|
if (attributes[0].Type != null)
|
||||||
|
{
|
||||||
|
extensibleType = attributes[0].Type;
|
||||||
|
m_extensibleServiceTypes[managedType.Name] = extensibleType;
|
||||||
|
}
|
||||||
|
else if (!String.IsNullOrEmpty(attributes[0].TypeName))
|
||||||
|
{
|
||||||
|
extensibleType = Type.GetType(attributes[0].TypeName);
|
||||||
|
|
||||||
|
if (extensibleType != null)
|
||||||
|
{
|
||||||
|
m_extensibleServiceTypes[managedType.Name] = extensibleType;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.Assert(false, "VssConnection: Could not load type from type name: " + attributes[0].TypeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extensibleType == null)
|
||||||
|
{
|
||||||
|
throw new ExtensibleServiceTypeNotRegisteredException(managedType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!managedType.GetTypeInfo().IsAssignableFrom(extensibleType.GetTypeInfo()))
|
||||||
|
{
|
||||||
|
throw new ExtensibleServiceTypeNotValidException(managedType, extensibleType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return extensibleType;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return managedType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (!m_isDisposed)
|
||||||
|
{
|
||||||
|
lock (m_disposeLock)
|
||||||
|
{
|
||||||
|
if (!m_isDisposed)
|
||||||
|
{
|
||||||
|
m_isDisposed = true;
|
||||||
|
foreach (var cachedType in m_cachedTypes.Values.Where(v => v is IDisposable).Select(v => v as IDisposable))
|
||||||
|
{
|
||||||
|
cachedType.Dispose();
|
||||||
|
}
|
||||||
|
m_cachedTypes.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckForDisposed()
|
||||||
|
{
|
||||||
|
if (m_isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool m_isDisposed = false;
|
||||||
|
private object m_disposeLock = new object();
|
||||||
|
private readonly ConcurrentDictionary<String, Type> m_extensibleServiceTypes = new ConcurrentDictionary<String, Type>();
|
||||||
|
private readonly Uri m_baseUrl;
|
||||||
|
private readonly HttpMessageHandler m_pipeline;
|
||||||
|
private readonly IEnumerable<DelegatingHandler> m_delegatingHandlers;
|
||||||
|
private readonly RawHttpMessageHandler m_innerHandler;
|
||||||
|
private readonly ConcurrentDictionary<Type, AsyncLock> m_loadingTypes = new ConcurrentDictionary<Type, AsyncLock>();
|
||||||
|
private readonly ConcurrentDictionary<Type, Object> m_cachedTypes = new ConcurrentDictionary<Type, Object>();
|
||||||
|
}
|
||||||
|
}
|
||||||
352
src/Sdk/WebApi/WebApi/RawHttpClientBase.cs
Normal file
352
src/Sdk/WebApi/WebApi/RawHttpClientBase.cs
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Formatting;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GitHub.Services.Common;
|
||||||
|
using GitHub.Services.Common.Diagnostics;
|
||||||
|
using GitHub.Services.OAuth;
|
||||||
|
using GitHub.Services.WebApi;
|
||||||
|
using GitHub.Services.WebApi.Utilities.Internal;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace Sdk.WebApi.WebApi
|
||||||
|
{
|
||||||
|
public class RawHttpClientBase: IDisposable
|
||||||
|
{
|
||||||
|
protected RawHttpClientBase(
|
||||||
|
Uri baseUrl,
|
||||||
|
VssOAuthCredential credentials)
|
||||||
|
: this(baseUrl, credentials, settings: null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RawHttpClientBase(
|
||||||
|
Uri baseUrl,
|
||||||
|
VssOAuthCredential credentials,
|
||||||
|
RawClientHttpRequestSettings settings)
|
||||||
|
: this(baseUrl, credentials, settings: settings, handlers: null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RawHttpClientBase(
|
||||||
|
Uri baseUrl,
|
||||||
|
VssOAuthCredential credentials,
|
||||||
|
params DelegatingHandler[] handlers)
|
||||||
|
: this(baseUrl, credentials, null, handlers)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RawHttpClientBase(
|
||||||
|
Uri baseUrl,
|
||||||
|
VssOAuthCredential credentials,
|
||||||
|
RawClientHttpRequestSettings settings,
|
||||||
|
params DelegatingHandler[] handlers)
|
||||||
|
: this(baseUrl, BuildHandler(credentials, settings, handlers), disposeHandler: true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RawHttpClientBase(
|
||||||
|
Uri baseUrl,
|
||||||
|
HttpMessageHandler pipeline,
|
||||||
|
bool disposeHandler)
|
||||||
|
{
|
||||||
|
m_client = new HttpClient(pipeline, disposeHandler);
|
||||||
|
|
||||||
|
// Disable their timeout since we handle it ourselves
|
||||||
|
m_client.Timeout = TimeSpan.FromMilliseconds(-1.0);
|
||||||
|
m_client.BaseAddress = baseUrl;
|
||||||
|
m_formatter = new VssJsonMediaTypeFormatter();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (!m_isDisposed)
|
||||||
|
{
|
||||||
|
lock (m_disposeLock)
|
||||||
|
{
|
||||||
|
if (!m_isDisposed)
|
||||||
|
{
|
||||||
|
m_isDisposed = true;
|
||||||
|
m_client.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public static TimeSpan TestDelay { get; set; }
|
||||||
|
|
||||||
|
protected async Task<HttpResponseMessage> SendAsync(
|
||||||
|
HttpMethod method,
|
||||||
|
Uri requestUri,
|
||||||
|
HttpContent content = null,
|
||||||
|
IEnumerable<KeyValuePair<String, String>> queryParameters = null,
|
||||||
|
Object userState = null,
|
||||||
|
CancellationToken cancellationToken = default(CancellationToken))
|
||||||
|
{
|
||||||
|
using (VssTraceActivity.GetOrCreate().EnterCorrelationScope())
|
||||||
|
using (HttpRequestMessage requestMessage = CreateRequestMessage(method, null, requestUri, content, queryParameters))
|
||||||
|
{
|
||||||
|
return await SendAsync(requestMessage, userState, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task<T> SendAsync<T>(
|
||||||
|
HttpMethod method,
|
||||||
|
IEnumerable<KeyValuePair<String, String>> additionalHeaders,
|
||||||
|
Uri requestUri,
|
||||||
|
HttpContent content = null,
|
||||||
|
IEnumerable<KeyValuePair<String, String>> queryParameters = null,
|
||||||
|
Object userState = null,
|
||||||
|
CancellationToken cancellationToken = default(CancellationToken))
|
||||||
|
{
|
||||||
|
using (VssTraceActivity.GetOrCreate().EnterCorrelationScope())
|
||||||
|
using (HttpRequestMessage requestMessage = CreateRequestMessage(method, additionalHeaders, requestUri, content, queryParameters))
|
||||||
|
{
|
||||||
|
return await SendAsync<T>(requestMessage, userState, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task<T> SendAsync<T>(
|
||||||
|
HttpRequestMessage message,
|
||||||
|
Object userState = null,
|
||||||
|
CancellationToken cancellationToken = default(CancellationToken))
|
||||||
|
{
|
||||||
|
//ConfigureAwait(false) enables the continuation to be run outside
|
||||||
|
//any captured SyncronizationContext (such as ASP.NET's) which keeps things
|
||||||
|
//from deadlocking...
|
||||||
|
using (HttpResponseMessage response = await this.SendAsync(message, userState, cancellationToken).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
return await ReadContentAsAsync<T>(response, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Task<HttpResponseMessage> SendAsync(
|
||||||
|
HttpRequestMessage message,
|
||||||
|
Object userState = null,
|
||||||
|
CancellationToken cancellationToken = default(CancellationToken))
|
||||||
|
{
|
||||||
|
// the default in httpClient for HttpCompletionOption is ResponseContentRead so that is what we do here
|
||||||
|
return this.SendAsync(
|
||||||
|
message,
|
||||||
|
/*completionOption:*/ HttpCompletionOption.ResponseContentRead,
|
||||||
|
userState,
|
||||||
|
cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task<HttpResponseMessage> SendAsync(
|
||||||
|
HttpRequestMessage message,
|
||||||
|
HttpCompletionOption completionOption,
|
||||||
|
Object userState = null,
|
||||||
|
CancellationToken cancellationToken = default(CancellationToken))
|
||||||
|
{
|
||||||
|
CheckForDisposed();
|
||||||
|
if (message.Headers.UserAgent != null)
|
||||||
|
{
|
||||||
|
foreach (ProductInfoHeaderValue headerValue in UserAgentUtility.GetDefaultRestUserAgent())
|
||||||
|
{
|
||||||
|
if (!message.Headers.UserAgent.Contains(headerValue))
|
||||||
|
{
|
||||||
|
message.Headers.UserAgent.Add(headerValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VssTraceActivity traceActivity = VssTraceActivity.GetOrCreate();
|
||||||
|
using (traceActivity.EnterCorrelationScope())
|
||||||
|
{
|
||||||
|
VssHttpEventSource.Log.HttpRequestStart(traceActivity, message);
|
||||||
|
message.Trace();
|
||||||
|
HttpResponseMessage response = await Client.SendAsync(message, completionOption, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
// Inject delay or failure for testing
|
||||||
|
if (TestDelay != TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
await ProcessDelayAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Trace();
|
||||||
|
VssHttpEventSource.Log.HttpRequestStop(VssTraceActivity.Current, response);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task<T> ReadContentAsAsync<T>(HttpResponseMessage response, CancellationToken cancellationToken = default(CancellationToken))
|
||||||
|
{
|
||||||
|
CheckForDisposed();
|
||||||
|
Boolean isJson = IsJsonResponse(response);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//deal with wrapped collections in json
|
||||||
|
if (isJson &&
|
||||||
|
typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo()) &&
|
||||||
|
!typeof(Byte[]).GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo()) &&
|
||||||
|
!typeof(JObject).GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo()))
|
||||||
|
{
|
||||||
|
var wrapper = await ReadJsonContentAsync<VssJsonCollectionWrapper<T>>(response, cancellationToken).ConfigureAwait(false);
|
||||||
|
return wrapper.Value;
|
||||||
|
}
|
||||||
|
else if (isJson)
|
||||||
|
{
|
||||||
|
return await ReadJsonContentAsync<T>(response, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (JsonReaderException)
|
||||||
|
{
|
||||||
|
// We thought the content was JSON but failed to parse.
|
||||||
|
// We ignore for now
|
||||||
|
}
|
||||||
|
|
||||||
|
return default(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual async Task<T> ReadJsonContentAsync<T>(HttpResponseMessage response, CancellationToken cancellationToken = default(CancellationToken))
|
||||||
|
{
|
||||||
|
return await response.Content.ReadAsAsync<T>(new[] { m_formatter }, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected HttpRequestMessage CreateRequestMessage(
|
||||||
|
HttpMethod method,
|
||||||
|
IEnumerable<KeyValuePair<String, String>> additionalHeaders,
|
||||||
|
Uri requestUri,
|
||||||
|
HttpContent content = null,
|
||||||
|
IEnumerable<KeyValuePair<String, String>> queryParameters = null,
|
||||||
|
String mediaType = c_jsonMediaType)
|
||||||
|
{
|
||||||
|
CheckForDisposed();
|
||||||
|
if (queryParameters != null && queryParameters.Any())
|
||||||
|
{
|
||||||
|
requestUri = requestUri.AppendQuery(queryParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpRequestMessage requestMessage = new HttpRequestMessage(method, requestUri.AbsoluteUri);
|
||||||
|
|
||||||
|
MediaTypeWithQualityHeaderValue acceptType = new MediaTypeWithQualityHeaderValue(mediaType);
|
||||||
|
requestMessage.Headers.Accept.Add(acceptType);
|
||||||
|
if (additionalHeaders != null)
|
||||||
|
{
|
||||||
|
foreach (KeyValuePair<String, String> kvp in additionalHeaders)
|
||||||
|
{
|
||||||
|
requestMessage.Headers.Add(kvp.Key, kvp.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content != null)
|
||||||
|
{
|
||||||
|
requestMessage.Content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The inner client.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Note to implementers: You should not update or expose the inner client
|
||||||
|
/// unless you instantiate your own instance of this class. Getting
|
||||||
|
/// an instance of this class from method such as GetClient<T>
|
||||||
|
/// a cached and shared instance.
|
||||||
|
/// </remarks>
|
||||||
|
protected HttpClient Client
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return m_client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The media type formatter.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Note to implementers: You should not update or expose the media type formatter
|
||||||
|
/// unless you instantiate your own instance of this class. Getting
|
||||||
|
/// an instance of this class from method such as GetClient<T>
|
||||||
|
/// a cached and shared instance.
|
||||||
|
/// </remarks>
|
||||||
|
protected MediaTypeFormatter Formatter
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return m_formatter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HttpMessageHandler BuildHandler(VssOAuthCredential credentials, RawClientHttpRequestSettings settings, DelegatingHandler[] handlers)
|
||||||
|
{
|
||||||
|
RawHttpMessageHandler innerHandler = new RawHttpMessageHandler(credentials, settings ?? new RawClientHttpRequestSettings());
|
||||||
|
|
||||||
|
if (null == handlers ||
|
||||||
|
0 == handlers.Length)
|
||||||
|
{
|
||||||
|
return innerHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpClientFactory.CreatePipeline(innerHandler, handlers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckForDisposed()
|
||||||
|
{
|
||||||
|
if (m_isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException(this.GetType().Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ProcessDelayAsync()
|
||||||
|
{
|
||||||
|
await Task.Delay(Math.Abs((Int32)TestDelay.TotalMilliseconds)).ConfigureAwait(false);
|
||||||
|
if (TestDelay < TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
throw new Exception("User injected failure.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Boolean IsJsonResponse(
|
||||||
|
HttpResponseMessage response)
|
||||||
|
{
|
||||||
|
if (HasContent(response)
|
||||||
|
&& response.Content.Headers != null && response.Content.Headers.ContentType != null
|
||||||
|
&& !String.IsNullOrEmpty(response.Content.Headers.ContentType.MediaType))
|
||||||
|
{
|
||||||
|
return (0 == String.Compare("application/json", response.Content.Headers.ContentType.MediaType, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Boolean HasContent(HttpResponseMessage response)
|
||||||
|
{
|
||||||
|
if (response != null &&
|
||||||
|
response.StatusCode != HttpStatusCode.NoContent &&
|
||||||
|
response.RequestMessage?.Method != HttpMethod.Head &&
|
||||||
|
response.Content?.Headers != null &&
|
||||||
|
(!response.Content.Headers.ContentLength.HasValue ||
|
||||||
|
(response.Content.Headers.ContentLength.HasValue && response.Content.Headers.ContentLength != 0)))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly HttpClient m_client;
|
||||||
|
private MediaTypeFormatter m_formatter;
|
||||||
|
private bool m_isDisposed = false;
|
||||||
|
private object m_disposeLock = new object();
|
||||||
|
private const String c_jsonMediaType = "application/json";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
{
|
{
|
||||||
Tracing trace = hc.GetTrace();
|
Tracing trace = hc.GetTrace();
|
||||||
|
|
||||||
CommandLineParser clp = new CommandLineParser(hc, secretArgNames: new string[0]);
|
CommandLineParser clp = new(hc, secretArgNames: new string[0]);
|
||||||
trace.Info("Constructed");
|
trace.Info("Constructed");
|
||||||
|
|
||||||
Assert.NotNull(clp);
|
Assert.NotNull(clp);
|
||||||
@@ -30,7 +30,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
using (TestHostContext hc = CreateTestContext())
|
using (TestHostContext hc = CreateTestContext())
|
||||||
{
|
{
|
||||||
// Arrange.
|
// Arrange.
|
||||||
CommandLineParser clp = new CommandLineParser(
|
CommandLineParser clp = new(
|
||||||
hc,
|
hc,
|
||||||
secretArgNames: new[] { "SecretArg1", "SecretArg2" });
|
secretArgNames: new[] { "SecretArg1", "SecretArg2" });
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
{
|
{
|
||||||
Tracing trace = hc.GetTrace();
|
Tracing trace = hc.GetTrace();
|
||||||
|
|
||||||
CommandLineParser clp = new CommandLineParser(hc, secretArgNames: new string[0]);
|
CommandLineParser clp = new(hc, secretArgNames: new string[0]);
|
||||||
trace.Info("Constructed.");
|
trace.Info("Constructed.");
|
||||||
|
|
||||||
clp.Parse(new string[] { "cmd1", "cmd2", "--arg1", "arg1val", "badcmd" });
|
clp.Parse(new string[] { "cmd1", "cmd2", "--arg1", "arg1val", "badcmd" });
|
||||||
@@ -81,7 +81,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
{
|
{
|
||||||
Tracing trace = hc.GetTrace();
|
Tracing trace = hc.GetTrace();
|
||||||
|
|
||||||
CommandLineParser clp = new CommandLineParser(hc, secretArgNames: new string[0]);
|
CommandLineParser clp = new(hc, secretArgNames: new string[0]);
|
||||||
trace.Info("Constructed.");
|
trace.Info("Constructed.");
|
||||||
|
|
||||||
clp.Parse(new string[] { "cmd1", "--arg1", "arg1val", "--arg2", "arg2val" });
|
clp.Parse(new string[] { "cmd1", "--arg1", "arg1val", "--arg2", "arg2val" });
|
||||||
@@ -105,7 +105,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
{
|
{
|
||||||
Tracing trace = hc.GetTrace();
|
Tracing trace = hc.GetTrace();
|
||||||
|
|
||||||
CommandLineParser clp = new CommandLineParser(hc, secretArgNames: new string[0]);
|
CommandLineParser clp = new(hc, secretArgNames: new string[0]);
|
||||||
trace.Info("Constructed.");
|
trace.Info("Constructed.");
|
||||||
|
|
||||||
clp.Parse(new string[] { "cmd1", "--flag1", "--arg1", "arg1val", "--flag2" });
|
clp.Parse(new string[] { "cmd1", "--flag1", "--arg1", "arg1val", "--flag2" });
|
||||||
@@ -120,7 +120,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
|
|
||||||
private TestHostContext CreateTestContext([CallerMemberName] string testName = "")
|
private TestHostContext CreateTestContext([CallerMemberName] string testName = "")
|
||||||
{
|
{
|
||||||
TestHostContext hc = new TestHostContext(this, testName);
|
TestHostContext hc = new(this, testName);
|
||||||
return hc;
|
return hc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public void BuildConstantGenerateSucceed()
|
public void BuildConstantGenerateSucceed()
|
||||||
{
|
{
|
||||||
List<string> validPackageNames = new List<string>()
|
List<string> validPackageNames = new()
|
||||||
{
|
{
|
||||||
"win-x64",
|
"win-x64",
|
||||||
"win-x86",
|
"win-x86",
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ namespace GitHub.Runner.Common.Tests.Worker.Container
|
|||||||
public void MountVolumeConstructorParsesStringInput()
|
public void MountVolumeConstructorParsesStringInput()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
MountVolume target = new MountVolume("/dst/dir"); // Maps anonymous Docker volume into target dir
|
MountVolume target = new("/dst/dir"); // Maps anonymous Docker volume into target dir
|
||||||
MountVolume source_target = new MountVolume("/src/dir:/dst/dir"); // Maps source to target dir
|
MountVolume source_target = new("/src/dir:/dst/dir"); // Maps source to target dir
|
||||||
MountVolume target_ro = new MountVolume("/dst/dir:ro");
|
MountVolume target_ro = new("/dst/dir:ro");
|
||||||
MountVolume source_target_ro = new MountVolume("/src/dir:/dst/dir:ro");
|
MountVolume source_target_ro = new("/src/dir:/dst/dir:ro");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Null(target.SourceVolumePath);
|
Assert.Null(target.SourceVolumePath);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
|
|
||||||
string shDownloadUrl = "https://dot.net/v1/dotnet-install.sh";
|
string shDownloadUrl = "https://dot.net/v1/dotnet-install.sh";
|
||||||
|
|
||||||
using (HttpClient downloadClient = new HttpClient())
|
using (HttpClient downloadClient = new())
|
||||||
{
|
{
|
||||||
var response = await downloadClient.GetAsync("https://www.bing.com");
|
var response = await downloadClient.GetAsync("https://www.bing.com");
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
@@ -51,7 +51,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
|
|
||||||
string ps1DownloadUrl = "https://dot.net/v1/dotnet-install.ps1";
|
string ps1DownloadUrl = "https://dot.net/v1/dotnet-install.ps1";
|
||||||
|
|
||||||
using (HttpClient downloadClient = new HttpClient())
|
using (HttpClient downloadClient = new())
|
||||||
{
|
{
|
||||||
var response = await downloadClient.GetAsync("https://www.bing.com");
|
var response = await downloadClient.GetAsync("https://www.bing.com");
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
[Trait("Category", "Common")]
|
[Trait("Category", "Common")]
|
||||||
public void LoadsTypeFromString()
|
public void LoadsTypeFromString()
|
||||||
{
|
{
|
||||||
using (TestHostContext tc = new TestHostContext(this))
|
using (TestHostContext tc = new(this))
|
||||||
{
|
{
|
||||||
// Arrange.
|
// Arrange.
|
||||||
var manager = new ExtensionManager();
|
var manager = new ExtensionManager();
|
||||||
@@ -34,7 +34,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
[Trait("Category", "Common")]
|
[Trait("Category", "Common")]
|
||||||
public void LoadsTypes()
|
public void LoadsTypes()
|
||||||
{
|
{
|
||||||
using (TestHostContext tc = new TestHostContext(this))
|
using (TestHostContext tc = new(this))
|
||||||
{
|
{
|
||||||
// Arrange.
|
// Arrange.
|
||||||
var manager = new ExtensionManager();
|
var manager = new ExtensionManager();
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
{
|
{
|
||||||
public sealed class CommandSettingsL0
|
public sealed class CommandSettingsL0
|
||||||
{
|
{
|
||||||
private readonly Mock<IPromptManager> _promptManager = new Mock<IPromptManager>();
|
private readonly Mock<IPromptManager> _promptManager = new();
|
||||||
|
|
||||||
// It is sufficient to test one arg only. All individual args are tested by the PromptsFor___ methods.
|
// It is sufficient to test one arg only. All individual args are tested by the PromptsFor___ methods.
|
||||||
// The PromptsFor___ methods suffice to cover the interesting differences between each of the args.
|
// The PromptsFor___ methods suffice to cover the interesting differences between each of the args.
|
||||||
@@ -879,7 +879,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
|
|
||||||
private TestHostContext CreateTestContext([CallerMemberName] string testName = "")
|
private TestHostContext CreateTestContext([CallerMemberName] string testName = "")
|
||||||
{
|
{
|
||||||
TestHostContext hc = new TestHostContext(this, testName);
|
TestHostContext hc = new(this, testName);
|
||||||
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
||||||
return hc;
|
return hc;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
[Trait("Category", "ArgumentValidator")]
|
[Trait("Category", "ArgumentValidator")]
|
||||||
public void ServerUrlValidator()
|
public void ServerUrlValidator()
|
||||||
{
|
{
|
||||||
using (TestHostContext hc = new TestHostContext(this))
|
using (TestHostContext hc = new(this))
|
||||||
{
|
{
|
||||||
Assert.True(Validators.ServerUrlValidator("http://servername"));
|
Assert.True(Validators.ServerUrlValidator("http://servername"));
|
||||||
Assert.False(Validators.ServerUrlValidator("Fail"));
|
Assert.False(Validators.ServerUrlValidator("Fail"));
|
||||||
@@ -23,7 +23,7 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
[Trait("Category", "ArgumentValidator")]
|
[Trait("Category", "ArgumentValidator")]
|
||||||
public void AuthSchemeValidator()
|
public void AuthSchemeValidator()
|
||||||
{
|
{
|
||||||
using (TestHostContext hc = new TestHostContext(this))
|
using (TestHostContext hc = new(this))
|
||||||
{
|
{
|
||||||
Assert.True(Validators.AuthSchemeValidator("OAuth"));
|
Assert.True(Validators.AuthSchemeValidator("OAuth"));
|
||||||
Assert.False(Validators.AuthSchemeValidator("Fail"));
|
Assert.False(Validators.AuthSchemeValidator("Fail"));
|
||||||
@@ -35,7 +35,7 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
[Trait("Category", "ArgumentValidator")]
|
[Trait("Category", "ArgumentValidator")]
|
||||||
public void NonEmptyValidator()
|
public void NonEmptyValidator()
|
||||||
{
|
{
|
||||||
using (TestHostContext hc = new TestHostContext(this))
|
using (TestHostContext hc = new(this))
|
||||||
{
|
{
|
||||||
Assert.True(Validators.NonEmptyValidator("test"));
|
Assert.True(Validators.NonEmptyValidator("test"));
|
||||||
Assert.False(Validators.NonEmptyValidator(string.Empty));
|
Assert.False(Validators.NonEmptyValidator(string.Empty));
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
private int _defaultRunnerGroupId = 1;
|
private int _defaultRunnerGroupId = 1;
|
||||||
private int _secondRunnerGroupId = 2;
|
private int _secondRunnerGroupId = 2;
|
||||||
private RSACryptoServiceProvider rsa = null;
|
private RSACryptoServiceProvider rsa = null;
|
||||||
private RunnerSettings _configMgrAgentSettings = new RunnerSettings();
|
private RunnerSettings _configMgrAgentSettings = new();
|
||||||
|
|
||||||
public ConfigurationManagerL0()
|
public ConfigurationManagerL0()
|
||||||
{
|
{
|
||||||
@@ -113,7 +113,7 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
|
|
||||||
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
|
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
|
||||||
{
|
{
|
||||||
TestHostContext tc = new TestHostContext(this, testName);
|
TestHostContext tc = new(this, testName);
|
||||||
tc.SetSingleton<ICredentialManager>(_credMgr.Object);
|
tc.SetSingleton<ICredentialManager>(_credMgr.Object);
|
||||||
tc.SetSingleton<IPromptManager>(_promptManager.Object);
|
tc.SetSingleton<IPromptManager>(_promptManager.Object);
|
||||||
tc.SetSingleton<IConfigurationStore>(_store.Object);
|
tc.SetSingleton<IConfigurationStore>(_store.Object);
|
||||||
@@ -234,5 +234,103 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
_runnerServer.Verify(x => x.GetAgentPoolsAsync(It.IsAny<string>(), It.Is<TaskAgentPoolType>(p => p == TaskAgentPoolType.Automation)), Times.Exactly(1));
|
_runnerServer.Verify(x => x.GetAgentPoolsAsync(It.IsAny<string>(), It.Is<TaskAgentPoolType>(p => p == TaskAgentPoolType.Automation)), Times.Exactly(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if OS_LINUX
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "ConfigurationManagement")]
|
||||||
|
public async Task ConfigureRunnerServiceFailsOnUnconfiguredRunners()
|
||||||
|
{
|
||||||
|
using (TestHostContext tc = CreateTestContext())
|
||||||
|
{
|
||||||
|
Tracing trace = tc.GetTrace();
|
||||||
|
|
||||||
|
trace.Info("Creating config manager");
|
||||||
|
IConfigurationManager configManager = new ConfigurationManager();
|
||||||
|
configManager.Initialize(tc);
|
||||||
|
|
||||||
|
trace.Info("Preparing command line arguments");
|
||||||
|
var command = new CommandSettings(
|
||||||
|
tc,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
"configure",
|
||||||
|
"--generateServiceConfig",
|
||||||
|
});
|
||||||
|
trace.Info("Constructed");
|
||||||
|
_store.Setup(x => x.IsConfigured()).Returns(false);
|
||||||
|
|
||||||
|
trace.Info("Ensuring service generation mode fails when on un-configured runners");
|
||||||
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => configManager.ConfigureAsync(command));
|
||||||
|
|
||||||
|
Assert.Contains("requires that the runner is already configured", ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "ConfigurationManagement")]
|
||||||
|
public async Task ConfigureRunnerServiceCreatesService()
|
||||||
|
{
|
||||||
|
using (TestHostContext tc = CreateTestContext())
|
||||||
|
{
|
||||||
|
Tracing trace = tc.GetTrace();
|
||||||
|
|
||||||
|
trace.Info("Creating config manager");
|
||||||
|
IConfigurationManager configManager = new ConfigurationManager();
|
||||||
|
configManager.Initialize(tc);
|
||||||
|
|
||||||
|
trace.Info("Preparing command line arguments");
|
||||||
|
var command = new CommandSettings(
|
||||||
|
tc,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
"configure",
|
||||||
|
"--generateServiceConfig",
|
||||||
|
});
|
||||||
|
trace.Info("Constructed");
|
||||||
|
|
||||||
|
_store.Setup(x => x.IsConfigured()).Returns(true);
|
||||||
|
|
||||||
|
trace.Info("Ensuring service generation mode fails when on un-configured runners");
|
||||||
|
await configManager.ConfigureAsync(command);
|
||||||
|
|
||||||
|
_serviceControlManager.Verify(x => x.GenerateScripts(It.IsAny<RunnerSettings>()), Times.Once);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !OS_LINUX
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "ConfigurationManagement")]
|
||||||
|
public async Task ConfigureRunnerServiceFailsOnUnsupportedPlatforms()
|
||||||
|
{
|
||||||
|
using (TestHostContext tc = CreateTestContext())
|
||||||
|
{
|
||||||
|
Tracing trace = tc.GetTrace();
|
||||||
|
|
||||||
|
trace.Info("Creating config manager");
|
||||||
|
IConfigurationManager configManager = new ConfigurationManager();
|
||||||
|
configManager.Initialize(tc);
|
||||||
|
|
||||||
|
trace.Info("Preparing command line arguments");
|
||||||
|
var command = new CommandSettings(
|
||||||
|
tc,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
"configure",
|
||||||
|
"--generateServiceConfig",
|
||||||
|
});
|
||||||
|
trace.Info("Constructed");
|
||||||
|
_store.Setup(x => x.IsConfigured()).Returns(true);
|
||||||
|
|
||||||
|
trace.Info("Ensuring service generation mode fails on unsupported runner platforms");
|
||||||
|
var ex = await Assert.ThrowsAsync<NotSupportedException>(() => configManager.ConfigureAsync(command));
|
||||||
|
|
||||||
|
Assert.Contains("only supported on Linux", ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
{
|
{
|
||||||
private readonly string _argName = "SomeArgName";
|
private readonly string _argName = "SomeArgName";
|
||||||
private readonly string _description = "Some description";
|
private readonly string _description = "Some description";
|
||||||
private readonly PromptManager _promptManager = new PromptManager();
|
private readonly PromptManager _promptManager = new();
|
||||||
private readonly Mock<ITerminal> _terminal = new Mock<ITerminal>();
|
private readonly Mock<ITerminal> _terminal = new();
|
||||||
private readonly string _unattendedExceptionMessage = "Invalid configuration provided for SomeArgName. Terminating unattended configuration.";
|
private readonly string _unattendedExceptionMessage = "Invalid configuration provided for SomeArgName. Terminating unattended configuration.";
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -21,7 +21,7 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
[Trait("Category", "PromptManager")]
|
[Trait("Category", "PromptManager")]
|
||||||
public void FallsBackToDefault()
|
public void FallsBackToDefault()
|
||||||
{
|
{
|
||||||
using (TestHostContext hc = new TestHostContext(this))
|
using (TestHostContext hc = new(this))
|
||||||
{
|
{
|
||||||
// Arrange.
|
// Arrange.
|
||||||
_terminal
|
_terminal
|
||||||
@@ -46,7 +46,7 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
[Trait("Category", "PromptManager")]
|
[Trait("Category", "PromptManager")]
|
||||||
public void FallsBackToDefaultWhenTrimmed()
|
public void FallsBackToDefaultWhenTrimmed()
|
||||||
{
|
{
|
||||||
using (TestHostContext hc = new TestHostContext(this))
|
using (TestHostContext hc = new(this))
|
||||||
{
|
{
|
||||||
// Arrange.
|
// Arrange.
|
||||||
_terminal
|
_terminal
|
||||||
@@ -71,7 +71,7 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
[Trait("Category", "PromptManager")]
|
[Trait("Category", "PromptManager")]
|
||||||
public void FallsBackToDefaultWhenUnattended()
|
public void FallsBackToDefaultWhenUnattended()
|
||||||
{
|
{
|
||||||
using (TestHostContext hc = new TestHostContext(this))
|
using (TestHostContext hc = new(this))
|
||||||
{
|
{
|
||||||
// Arrange.
|
// Arrange.
|
||||||
_terminal
|
_terminal
|
||||||
@@ -98,7 +98,7 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
[Trait("Category", "PromptManager")]
|
[Trait("Category", "PromptManager")]
|
||||||
public void Prompts()
|
public void Prompts()
|
||||||
{
|
{
|
||||||
using (TestHostContext hc = new TestHostContext(this))
|
using (TestHostContext hc = new(this))
|
||||||
{
|
{
|
||||||
// Arrange.
|
// Arrange.
|
||||||
_terminal
|
_terminal
|
||||||
@@ -123,7 +123,7 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
[Trait("Category", "PromptManager")]
|
[Trait("Category", "PromptManager")]
|
||||||
public void PromptsAgainWhenEmpty()
|
public void PromptsAgainWhenEmpty()
|
||||||
{
|
{
|
||||||
using (TestHostContext hc = new TestHostContext(this))
|
using (TestHostContext hc = new(this))
|
||||||
{
|
{
|
||||||
// Arrange.
|
// Arrange.
|
||||||
var readLineValues = new Queue<string>(new[] { string.Empty, "Some prompt value" });
|
var readLineValues = new Queue<string>(new[] { string.Empty, "Some prompt value" });
|
||||||
@@ -150,7 +150,7 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
[Trait("Category", "PromptManager")]
|
[Trait("Category", "PromptManager")]
|
||||||
public void PromptsAgainWhenFailsValidation()
|
public void PromptsAgainWhenFailsValidation()
|
||||||
{
|
{
|
||||||
using (TestHostContext hc = new TestHostContext(this))
|
using (TestHostContext hc = new(this))
|
||||||
{
|
{
|
||||||
// Arrange.
|
// Arrange.
|
||||||
var readLineValues = new Queue<string>(new[] { "Some invalid prompt value", "Some valid prompt value" });
|
var readLineValues = new Queue<string>(new[] { "Some invalid prompt value", "Some valid prompt value" });
|
||||||
@@ -177,7 +177,7 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
[Trait("Category", "PromptManager")]
|
[Trait("Category", "PromptManager")]
|
||||||
public void ThrowsWhenUnattended()
|
public void ThrowsWhenUnattended()
|
||||||
{
|
{
|
||||||
using (TestHostContext hc = new TestHostContext(this))
|
using (TestHostContext hc = new(this))
|
||||||
{
|
{
|
||||||
// Arrange.
|
// Arrange.
|
||||||
_terminal
|
_terminal
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
trace.Info("GetVssCredentials()");
|
trace.Info("GetVssCredentials()");
|
||||||
|
|
||||||
var loginCred = new VssOAuthAccessTokenCredential("sometoken");
|
var loginCred = new VssOAuthAccessTokenCredential("sometoken");
|
||||||
VssCredentials creds = new VssCredentials(loginCred);
|
VssCredentials creds = new(loginCred);
|
||||||
trace.Verbose("cred created");
|
trace.Verbose("cred created");
|
||||||
|
|
||||||
return creds;
|
return creds;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
|
|
||||||
private Pipelines.AgentJobRequestMessage CreateJobRequestMessage()
|
private Pipelines.AgentJobRequestMessage CreateJobRequestMessage()
|
||||||
{
|
{
|
||||||
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
|
TaskOrchestrationPlanReference plan = new();
|
||||||
TimelineReference timeline = null;
|
TimelineReference timeline = null;
|
||||||
Guid jobId = Guid.NewGuid();
|
Guid jobId = Guid.NewGuid();
|
||||||
var result = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "someJob", "someJob", 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);
|
var result = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "someJob", "someJob", 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);
|
||||||
@@ -101,10 +101,10 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequest));
|
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequest));
|
||||||
TaskCompletionSource<int> firstJobRequestRenewed = new TaskCompletionSource<int>();
|
TaskCompletionSource<int> firstJobRequestRenewed = new();
|
||||||
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
CancellationTokenSource cancellationTokenSource = new();
|
||||||
|
|
||||||
TaskAgentJobRequest request = new TaskAgentJobRequest();
|
TaskAgentJobRequest request = new();
|
||||||
PropertyInfo lockUntilProperty = request.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
PropertyInfo lockUntilProperty = request.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
Assert.NotNull(lockUntilProperty);
|
Assert.NotNull(lockUntilProperty);
|
||||||
lockUntilProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
|
lockUntilProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
|
||||||
@@ -159,10 +159,10 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestStopOnJobNotFoundExceptions));
|
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestStopOnJobNotFoundExceptions));
|
||||||
TaskCompletionSource<int> firstJobRequestRenewed = new TaskCompletionSource<int>();
|
TaskCompletionSource<int> firstJobRequestRenewed = new();
|
||||||
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
CancellationTokenSource cancellationTokenSource = new();
|
||||||
|
|
||||||
TaskAgentJobRequest request = new TaskAgentJobRequest();
|
TaskAgentJobRequest request = new();
|
||||||
PropertyInfo lockUntilProperty = request.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
PropertyInfo lockUntilProperty = request.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
Assert.NotNull(lockUntilProperty);
|
Assert.NotNull(lockUntilProperty);
|
||||||
lockUntilProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
|
lockUntilProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
|
||||||
@@ -218,10 +218,10 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestStopOnJobTokenExpiredExceptions));
|
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestStopOnJobTokenExpiredExceptions));
|
||||||
TaskCompletionSource<int> firstJobRequestRenewed = new TaskCompletionSource<int>();
|
TaskCompletionSource<int> firstJobRequestRenewed = new();
|
||||||
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
CancellationTokenSource cancellationTokenSource = new();
|
||||||
|
|
||||||
TaskAgentJobRequest request = new TaskAgentJobRequest();
|
TaskAgentJobRequest request = new();
|
||||||
PropertyInfo lockUntilProperty = request.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
PropertyInfo lockUntilProperty = request.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
Assert.NotNull(lockUntilProperty);
|
Assert.NotNull(lockUntilProperty);
|
||||||
lockUntilProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
|
lockUntilProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
|
||||||
@@ -279,8 +279,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
var reservedAgent = new TaskAgentReference { Name = newName };
|
var reservedAgent = new TaskAgentReference { Name = newName };
|
||||||
|
|
||||||
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestStopOnJobTokenExpiredExceptions));
|
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestStopOnJobTokenExpiredExceptions));
|
||||||
TaskCompletionSource<int> firstJobRequestRenewed = new TaskCompletionSource<int>();
|
TaskCompletionSource<int> firstJobRequestRenewed = new();
|
||||||
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
CancellationTokenSource cancellationTokenSource = new();
|
||||||
|
|
||||||
var request = new Mock<TaskAgentJobRequest>();
|
var request = new Mock<TaskAgentJobRequest>();
|
||||||
request.Object.ReservedAgent = reservedAgent;
|
request.Object.ReservedAgent = reservedAgent;
|
||||||
@@ -335,8 +335,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
var reservedAgent = new TaskAgentReference { Name = newName };
|
var reservedAgent = new TaskAgentReference { Name = newName };
|
||||||
|
|
||||||
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestStopOnJobTokenExpiredExceptions));
|
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestStopOnJobTokenExpiredExceptions));
|
||||||
TaskCompletionSource<int> firstJobRequestRenewed = new TaskCompletionSource<int>();
|
TaskCompletionSource<int> firstJobRequestRenewed = new();
|
||||||
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
CancellationTokenSource cancellationTokenSource = new();
|
||||||
|
|
||||||
var request = new Mock<TaskAgentJobRequest>();
|
var request = new Mock<TaskAgentJobRequest>();
|
||||||
request.Object.ReservedAgent = reservedAgent;
|
request.Object.ReservedAgent = reservedAgent;
|
||||||
@@ -388,8 +388,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
var oldSettings = new RunnerSettings { AgentName = oldName };
|
var oldSettings = new RunnerSettings { AgentName = oldName };
|
||||||
|
|
||||||
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestStopOnJobTokenExpiredExceptions));
|
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestStopOnJobTokenExpiredExceptions));
|
||||||
TaskCompletionSource<int> firstJobRequestRenewed = new TaskCompletionSource<int>();
|
TaskCompletionSource<int> firstJobRequestRenewed = new();
|
||||||
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
CancellationTokenSource cancellationTokenSource = new();
|
||||||
|
|
||||||
var request = new Mock<TaskAgentJobRequest>();
|
var request = new Mock<TaskAgentJobRequest>();
|
||||||
PropertyInfo lockUntilProperty = request.Object.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
PropertyInfo lockUntilProperty = request.Object.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
@@ -441,10 +441,10 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestRecoverFromExceptions));
|
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestRecoverFromExceptions));
|
||||||
TaskCompletionSource<int> firstJobRequestRenewed = new TaskCompletionSource<int>();
|
TaskCompletionSource<int> firstJobRequestRenewed = new();
|
||||||
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
CancellationTokenSource cancellationTokenSource = new();
|
||||||
|
|
||||||
TaskAgentJobRequest request = new TaskAgentJobRequest();
|
TaskAgentJobRequest request = new();
|
||||||
PropertyInfo lockUntilProperty = request.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
PropertyInfo lockUntilProperty = request.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
Assert.NotNull(lockUntilProperty);
|
Assert.NotNull(lockUntilProperty);
|
||||||
lockUntilProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
|
lockUntilProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
|
||||||
@@ -502,10 +502,10 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestFirstRenewRetrySixTimes));
|
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestFirstRenewRetrySixTimes));
|
||||||
TaskCompletionSource<int> firstJobRequestRenewed = new TaskCompletionSource<int>();
|
TaskCompletionSource<int> firstJobRequestRenewed = new();
|
||||||
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
CancellationTokenSource cancellationTokenSource = new();
|
||||||
|
|
||||||
TaskAgentJobRequest request = new TaskAgentJobRequest();
|
TaskAgentJobRequest request = new();
|
||||||
PropertyInfo lockUntilProperty = request.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
PropertyInfo lockUntilProperty = request.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
Assert.NotNull(lockUntilProperty);
|
Assert.NotNull(lockUntilProperty);
|
||||||
lockUntilProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
|
lockUntilProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
|
||||||
@@ -557,10 +557,10 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestStopOnExpiredRequest));
|
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestStopOnExpiredRequest));
|
||||||
TaskCompletionSource<int> firstJobRequestRenewed = new TaskCompletionSource<int>();
|
TaskCompletionSource<int> firstJobRequestRenewed = new();
|
||||||
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
CancellationTokenSource cancellationTokenSource = new();
|
||||||
|
|
||||||
TaskAgentJobRequest request = new TaskAgentJobRequest();
|
TaskAgentJobRequest request = new();
|
||||||
PropertyInfo lockUntilProperty = request.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
PropertyInfo lockUntilProperty = request.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
Assert.NotNull(lockUntilProperty);
|
Assert.NotNull(lockUntilProperty);
|
||||||
lockUntilProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
|
lockUntilProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
|
|
||||||
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
|
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
|
||||||
{
|
{
|
||||||
TestHostContext tc = new TestHostContext(this, testName);
|
TestHostContext tc = new(this, testName);
|
||||||
tc.SetSingleton<IConfigurationManager>(_config.Object);
|
tc.SetSingleton<IConfigurationManager>(_config.Object);
|
||||||
tc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
tc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
tc.SetSingleton<ICredentialManager>(_credMgr.Object);
|
tc.SetSingleton<ICredentialManager>(_credMgr.Object);
|
||||||
@@ -68,7 +68,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
MessageListener listener = new MessageListener();
|
MessageListener listener = new();
|
||||||
listener.Initialize(tc);
|
listener.Initialize(tc);
|
||||||
|
|
||||||
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||||
@@ -112,7 +112,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
MessageListener listener = new MessageListener();
|
MessageListener listener = new();
|
||||||
listener.Initialize(tc);
|
listener.Initialize(tc);
|
||||||
|
|
||||||
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||||
@@ -159,7 +159,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
MessageListener listener = new MessageListener();
|
MessageListener listener = new();
|
||||||
listener.Initialize(tc);
|
listener.Initialize(tc);
|
||||||
|
|
||||||
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||||
@@ -241,7 +241,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
MessageListener listener = new MessageListener();
|
MessageListener listener = new();
|
||||||
listener.Initialize(tc);
|
listener.Initialize(tc);
|
||||||
|
|
||||||
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||||
@@ -285,7 +285,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
MessageListener listener = new MessageListener();
|
MessageListener listener = new();
|
||||||
listener.Initialize(tc);
|
listener.Initialize(tc);
|
||||||
|
|
||||||
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
|
|
||||||
private Pipelines.AgentJobRequestMessage CreateJobRequestMessage(string jobName)
|
private Pipelines.AgentJobRequestMessage CreateJobRequestMessage(string jobName)
|
||||||
{
|
{
|
||||||
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
|
TaskOrchestrationPlanReference plan = new();
|
||||||
TimelineReference timeline = null;
|
TimelineReference timeline = null;
|
||||||
Guid jobId = Guid.NewGuid();
|
Guid jobId = Guid.NewGuid();
|
||||||
return new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "test", "test", 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);
|
return new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "test", "test", 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);
|
||||||
@@ -155,7 +155,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TheoryData<string[], bool, Times> RunAsServiceTestData = new TheoryData<string[], bool, Times>()
|
public static TheoryData<string[], bool, Times> RunAsServiceTestData = new()
|
||||||
{
|
{
|
||||||
// staring with run command, configured as run as service, should start the runner
|
// staring with run command, configured as run as service, should start the runner
|
||||||
{ new [] { "run" }, true, Times.Once() },
|
{ new [] { "run" }, true, Times.Once() },
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
private Mock<ITerminal> _term;
|
private Mock<ITerminal> _term;
|
||||||
private Mock<IConfigurationStore> _configStore;
|
private Mock<IConfigurationStore> _configStore;
|
||||||
private Mock<IJobDispatcher> _jobDispatcher;
|
private Mock<IJobDispatcher> _jobDispatcher;
|
||||||
private AgentRefreshMessage _refreshMessage = new AgentRefreshMessage(1, "2.299.0");
|
private AgentRefreshMessage _refreshMessage = new(1, "2.999.0");
|
||||||
private List<TrimmedPackageMetadata> _trimmedPackages = new List<TrimmedPackageMetadata>();
|
private List<TrimmedPackageMetadata> _trimmedPackages = new();
|
||||||
|
|
||||||
#if !OS_WINDOWS
|
#if !OS_WINDOWS
|
||||||
private string _packageUrl = null;
|
private string _packageUrl = null;
|
||||||
@@ -52,7 +52,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
if (response.StatusCode == System.Net.HttpStatusCode.Redirect)
|
if (response.StatusCode == System.Net.HttpStatusCode.Redirect)
|
||||||
{
|
{
|
||||||
var redirectUrl = response.Headers.Location.ToString();
|
var redirectUrl = response.Headers.Location.ToString();
|
||||||
Regex regex = new Regex(@"/runner/releases/tag/v(?<version>\d+\.\d+\.\d+)");
|
Regex regex = new(@"/runner/releases/tag/v(?<version>\d+\.\d+\.\d+)");
|
||||||
var match = regex.Match(redirectUrl);
|
var match = regex.Match(redirectUrl);
|
||||||
if (match.Success)
|
if (match.Success)
|
||||||
{
|
{
|
||||||
@@ -77,8 +77,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_trimmedPackages = StringUtil.ConvertFromJson<List<TrimmedPackageMetadata>>(json);
|
_trimmedPackages = StringUtil.ConvertFromJson<List<TrimmedPackageMetadata>>(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
_runnerServer.Setup(x => x.GetPackageAsync("agent", BuildConstants.RunnerPackage.PackageName, "2.299.0", true, It.IsAny<CancellationToken>()))
|
_runnerServer.Setup(x => x.GetPackageAsync("agent", BuildConstants.RunnerPackage.PackageName, "2.999.0", true, It.IsAny<CancellationToken>()))
|
||||||
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.299.0"), DownloadUrl = _packageUrl }));
|
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.999.0"), DownloadUrl = _packageUrl }));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,13 +127,13 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
{
|
{
|
||||||
var result = await updater.SelfUpdate(_refreshMessage, _jobDispatcher.Object, true, hc.RunnerShutdownToken);
|
var result = await updater.SelfUpdate(_refreshMessage, _jobDispatcher.Object, true, hc.RunnerShutdownToken);
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.299.0")));
|
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.999.0")));
|
||||||
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.299.0")));
|
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.999.0")));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.299.0"), CancellationToken.None);
|
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.999.0"), CancellationToken.None);
|
||||||
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.299.0"), CancellationToken.None);
|
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.999.0"), CancellationToken.None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,8 +219,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
hc.SetSingleton<IHttpClientHandlerFactory>(new HttpClientHandlerFactory());
|
hc.SetSingleton<IHttpClientHandlerFactory>(new HttpClientHandlerFactory());
|
||||||
|
|
||||||
_runnerServer.Setup(x => x.GetPackageAsync("agent", BuildConstants.RunnerPackage.PackageName, "2.299.0", true, It.IsAny<CancellationToken>()))
|
_runnerServer.Setup(x => x.GetPackageAsync("agent", BuildConstants.RunnerPackage.PackageName, "2.999.0", true, It.IsAny<CancellationToken>()))
|
||||||
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.299.0"), DownloadUrl = $"https://github.com/actions/runner/notexists" }));
|
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.999.0"), DownloadUrl = $"https://github.com/actions/runner/notexists" }));
|
||||||
|
|
||||||
var p1 = new ProcessInvokerWrapper();
|
var p1 = new ProcessInvokerWrapper();
|
||||||
p1.Initialize(hc);
|
p1.Initialize(hc);
|
||||||
@@ -274,8 +274,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
hc.SetSingleton<IHttpClientHandlerFactory>(new HttpClientHandlerFactory());
|
hc.SetSingleton<IHttpClientHandlerFactory>(new HttpClientHandlerFactory());
|
||||||
|
|
||||||
_runnerServer.Setup(x => x.GetPackageAsync("agent", BuildConstants.RunnerPackage.PackageName, "2.299.0", true, It.IsAny<CancellationToken>()))
|
_runnerServer.Setup(x => x.GetPackageAsync("agent", BuildConstants.RunnerPackage.PackageName, "2.999.0", true, It.IsAny<CancellationToken>()))
|
||||||
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.299.0"), DownloadUrl = _packageUrl, HashValue = "bad_hash" }));
|
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.999.0"), DownloadUrl = _packageUrl, HashValue = "bad_hash" }));
|
||||||
|
|
||||||
var p1 = new ProcessInvokerWrapper();
|
var p1 = new ProcessInvokerWrapper();
|
||||||
p1.Initialize(hc);
|
p1.Initialize(hc);
|
||||||
@@ -340,8 +340,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
hc.EnqueueInstance<IProcessInvoker>(p3);
|
hc.EnqueueInstance<IProcessInvoker>(p3);
|
||||||
updater.Initialize(hc);
|
updater.Initialize(hc);
|
||||||
|
|
||||||
_runnerServer.Setup(x => x.GetPackageAsync("agent", BuildConstants.RunnerPackage.PackageName, "2.299.0", true, It.IsAny<CancellationToken>()))
|
_runnerServer.Setup(x => x.GetPackageAsync("agent", BuildConstants.RunnerPackage.PackageName, "2.999.0", true, It.IsAny<CancellationToken>()))
|
||||||
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.299.0"), DownloadUrl = _packageUrl, TrimmedPackages = new List<TrimmedPackageMetadata>() { new TrimmedPackageMetadata() } }));
|
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.999.0"), DownloadUrl = _packageUrl, TrimmedPackages = new List<TrimmedPackageMetadata>() { new TrimmedPackageMetadata() } }));
|
||||||
|
|
||||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.Callback((int p, int a, string s, string t) =>
|
.Callback((int p, int a, string s, string t) =>
|
||||||
@@ -354,8 +354,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
{
|
{
|
||||||
var result = await updater.SelfUpdate(_refreshMessage, _jobDispatcher.Object, true, hc.RunnerShutdownToken);
|
var result = await updater.SelfUpdate(_refreshMessage, _jobDispatcher.Object, true, hc.RunnerShutdownToken);
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.299.0")));
|
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.999.0")));
|
||||||
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.299.0")));
|
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.999.0")));
|
||||||
|
|
||||||
FieldInfo contentHashesProperty = updater.GetType().GetField("_contentHashes", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
FieldInfo contentHashesProperty = updater.GetType().GetField("_contentHashes", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
Assert.NotNull(contentHashesProperty);
|
Assert.NotNull(contentHashesProperty);
|
||||||
@@ -370,8 +370,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.299.0"), CancellationToken.None);
|
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.999.0"), CancellationToken.None);
|
||||||
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.299.0"), CancellationToken.None);
|
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.999.0"), CancellationToken.None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -486,8 +486,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
updater.Initialize(hc);
|
updater.Initialize(hc);
|
||||||
|
|
||||||
var trim = _trimmedPackages.Where(x => !x.TrimmedContents.ContainsKey("dotnetRuntime")).ToList();
|
var trim = _trimmedPackages.Where(x => !x.TrimmedContents.ContainsKey("dotnetRuntime")).ToList();
|
||||||
_runnerServer.Setup(x => x.GetPackageAsync("agent", BuildConstants.RunnerPackage.PackageName, "2.299.0", true, It.IsAny<CancellationToken>()))
|
_runnerServer.Setup(x => x.GetPackageAsync("agent", BuildConstants.RunnerPackage.PackageName, "2.999.0", true, It.IsAny<CancellationToken>()))
|
||||||
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.299.0"), DownloadUrl = _packageUrl, TrimmedPackages = trim }));
|
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.999.0"), DownloadUrl = _packageUrl, TrimmedPackages = trim }));
|
||||||
|
|
||||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.Callback((int p, int a, string s, string t) =>
|
.Callback((int p, int a, string s, string t) =>
|
||||||
@@ -500,13 +500,13 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
{
|
{
|
||||||
var result = await updater.SelfUpdate(_refreshMessage, _jobDispatcher.Object, true, hc.RunnerShutdownToken);
|
var result = await updater.SelfUpdate(_refreshMessage, _jobDispatcher.Object, true, hc.RunnerShutdownToken);
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.299.0")));
|
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.999.0")));
|
||||||
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.299.0")));
|
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.999.0")));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.299.0"), CancellationToken.None);
|
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.999.0"), CancellationToken.None);
|
||||||
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.299.0"), CancellationToken.None);
|
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.999.0"), CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
var traceFile = Path.GetTempFileName();
|
var traceFile = Path.GetTempFileName();
|
||||||
@@ -575,8 +575,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
updater.Initialize(hc);
|
updater.Initialize(hc);
|
||||||
|
|
||||||
var trim = _trimmedPackages.Where(x => x.TrimmedContents.ContainsKey("dotnetRuntime") && x.TrimmedContents.ContainsKey("externals")).ToList();
|
var trim = _trimmedPackages.Where(x => x.TrimmedContents.ContainsKey("dotnetRuntime") && x.TrimmedContents.ContainsKey("externals")).ToList();
|
||||||
_runnerServer.Setup(x => x.GetPackageAsync("agent", BuildConstants.RunnerPackage.PackageName, "2.299.0", true, It.IsAny<CancellationToken>()))
|
_runnerServer.Setup(x => x.GetPackageAsync("agent", BuildConstants.RunnerPackage.PackageName, "2.999.0", true, It.IsAny<CancellationToken>()))
|
||||||
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.299.0"), DownloadUrl = _packageUrl, TrimmedPackages = trim }));
|
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.999.0"), DownloadUrl = _packageUrl, TrimmedPackages = trim }));
|
||||||
|
|
||||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.Callback((int p, int a, string s, string t) =>
|
.Callback((int p, int a, string s, string t) =>
|
||||||
@@ -589,13 +589,13 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
{
|
{
|
||||||
var result = await updater.SelfUpdate(_refreshMessage, _jobDispatcher.Object, true, hc.RunnerShutdownToken);
|
var result = await updater.SelfUpdate(_refreshMessage, _jobDispatcher.Object, true, hc.RunnerShutdownToken);
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.299.0")));
|
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.999.0")));
|
||||||
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.299.0")));
|
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.999.0")));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.299.0"), CancellationToken.None);
|
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.999.0"), CancellationToken.None);
|
||||||
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.299.0"), CancellationToken.None);
|
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.999.0"), CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
var traceFile = Path.GetTempFileName();
|
var traceFile = Path.GetTempFileName();
|
||||||
@@ -676,8 +676,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_runnerServer.Setup(x => x.GetPackageAsync("agent", BuildConstants.RunnerPackage.PackageName, "2.299.0", true, It.IsAny<CancellationToken>()))
|
_runnerServer.Setup(x => x.GetPackageAsync("agent", BuildConstants.RunnerPackage.PackageName, "2.999.0", true, It.IsAny<CancellationToken>()))
|
||||||
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.299.0"), DownloadUrl = _packageUrl, TrimmedPackages = trim }));
|
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.999.0"), DownloadUrl = _packageUrl, TrimmedPackages = trim }));
|
||||||
|
|
||||||
|
|
||||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||||
@@ -691,13 +691,13 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
{
|
{
|
||||||
var result = await updater.SelfUpdate(_refreshMessage, _jobDispatcher.Object, true, hc.RunnerShutdownToken);
|
var result = await updater.SelfUpdate(_refreshMessage, _jobDispatcher.Object, true, hc.RunnerShutdownToken);
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.299.0")));
|
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.999.0")));
|
||||||
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.299.0")));
|
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.999.0")));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.299.0"), CancellationToken.None);
|
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.999.0"), CancellationToken.None);
|
||||||
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.299.0"), CancellationToken.None);
|
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.999.0"), CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
var traceFile = Path.GetTempFileName();
|
var traceFile = Path.GetTempFileName();
|
||||||
@@ -754,8 +754,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
package.HashValue = "mismatch";
|
package.HashValue = "mismatch";
|
||||||
}
|
}
|
||||||
|
|
||||||
_runnerServer.Setup(x => x.GetPackageAsync("agent", BuildConstants.RunnerPackage.PackageName, "2.299.0", true, It.IsAny<CancellationToken>()))
|
_runnerServer.Setup(x => x.GetPackageAsync("agent", BuildConstants.RunnerPackage.PackageName, "2.999.0", true, It.IsAny<CancellationToken>()))
|
||||||
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.299.0"), DownloadUrl = _packageUrl, TrimmedPackages = trim }));
|
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.999.0"), DownloadUrl = _packageUrl, TrimmedPackages = trim }));
|
||||||
|
|
||||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.Callback((int p, int a, string s, string t) =>
|
.Callback((int p, int a, string s, string t) =>
|
||||||
@@ -768,13 +768,13 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
{
|
{
|
||||||
var result = await updater.SelfUpdate(_refreshMessage, _jobDispatcher.Object, true, hc.RunnerShutdownToken);
|
var result = await updater.SelfUpdate(_refreshMessage, _jobDispatcher.Object, true, hc.RunnerShutdownToken);
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.299.0")));
|
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.999.0")));
|
||||||
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.299.0")));
|
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.999.0")));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.299.0"), CancellationToken.None);
|
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.999.0"), CancellationToken.None);
|
||||||
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.299.0"), CancellationToken.None);
|
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.999.0"), CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
var traceFile = Path.GetTempFileName();
|
var traceFile = Path.GetTempFileName();
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
[Trait("Category", "Common")]
|
[Trait("Category", "Common")]
|
||||||
public async Task RunnerLayoutParts_NewFilesCrossAll()
|
public async Task RunnerLayoutParts_NewFilesCrossAll()
|
||||||
{
|
{
|
||||||
using (TestHostContext hc = new TestHostContext(this))
|
using (TestHostContext hc = new(this))
|
||||||
{
|
{
|
||||||
Tracing trace = hc.GetTrace();
|
Tracing trace = hc.GetTrace();
|
||||||
var runnerCoreAssetsFile = Path.Combine(TestUtil.GetSrcPath(), @"Misc/runnercoreassets");
|
var runnerCoreAssetsFile = Path.Combine(TestUtil.GetSrcPath(), @"Misc/runnercoreassets");
|
||||||
@@ -53,7 +53,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
[Trait("Category", "Common")]
|
[Trait("Category", "Common")]
|
||||||
public async Task RunnerLayoutParts_OverlapFiles()
|
public async Task RunnerLayoutParts_OverlapFiles()
|
||||||
{
|
{
|
||||||
using (TestHostContext hc = new TestHostContext(this))
|
using (TestHostContext hc = new(this))
|
||||||
{
|
{
|
||||||
Tracing trace = hc.GetTrace();
|
Tracing trace = hc.GetTrace();
|
||||||
var runnerCoreAssetsFile = Path.Combine(TestUtil.GetSrcPath(), @"Misc/runnercoreassets");
|
var runnerCoreAssetsFile = Path.Combine(TestUtil.GetSrcPath(), @"Misc/runnercoreassets");
|
||||||
@@ -77,7 +77,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
[Trait("Category", "Common")]
|
[Trait("Category", "Common")]
|
||||||
public async Task RunnerLayoutParts_NewRunnerCoreAssets()
|
public async Task RunnerLayoutParts_NewRunnerCoreAssets()
|
||||||
{
|
{
|
||||||
using (TestHostContext hc = new TestHostContext(this))
|
using (TestHostContext hc = new(this))
|
||||||
{
|
{
|
||||||
Tracing trace = hc.GetTrace();
|
Tracing trace = hc.GetTrace();
|
||||||
var runnerCoreAssetsFile = Path.Combine(TestUtil.GetSrcPath(), @"Misc/runnercoreassets");
|
var runnerCoreAssetsFile = Path.Combine(TestUtil.GetSrcPath(), @"Misc/runnercoreassets");
|
||||||
@@ -120,7 +120,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
[Trait("Category", "Common")]
|
[Trait("Category", "Common")]
|
||||||
public async Task RunnerLayoutParts_NewDotnetRuntimeAssets()
|
public async Task RunnerLayoutParts_NewDotnetRuntimeAssets()
|
||||||
{
|
{
|
||||||
using (TestHostContext hc = new TestHostContext(this))
|
using (TestHostContext hc = new(this))
|
||||||
{
|
{
|
||||||
Tracing trace = hc.GetTrace();
|
Tracing trace = hc.GetTrace();
|
||||||
var runnerDotnetRuntimeFile = Path.Combine(TestUtil.GetSrcPath(), @"Misc/runnerdotnetruntimeassets");
|
var runnerDotnetRuntimeFile = Path.Combine(TestUtil.GetSrcPath(), @"Misc/runnerdotnetruntimeassets");
|
||||||
@@ -156,7 +156,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
[Trait("Category", "Common")]
|
[Trait("Category", "Common")]
|
||||||
public async Task RunnerLayoutParts_CheckDotnetRuntimeHash()
|
public async Task RunnerLayoutParts_CheckDotnetRuntimeHash()
|
||||||
{
|
{
|
||||||
using (TestHostContext hc = new TestHostContext(this))
|
using (TestHostContext hc = new(this))
|
||||||
{
|
{
|
||||||
Tracing trace = hc.GetTrace();
|
Tracing trace = hc.GetTrace();
|
||||||
var dotnetRuntimeHashFile = Path.Combine(TestUtil.GetSrcPath(), $"Misc/contentHash/dotnetRuntime/{BuildConstants.RunnerPackage.PackageName}");
|
var dotnetRuntimeHashFile = Path.Combine(TestUtil.GetSrcPath(), $"Misc/contentHash/dotnetRuntime/{BuildConstants.RunnerPackage.PackageName}");
|
||||||
@@ -217,7 +217,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
[Trait("Category", "Common")]
|
[Trait("Category", "Common")]
|
||||||
public async Task RunnerLayoutParts_CheckExternalsHash()
|
public async Task RunnerLayoutParts_CheckExternalsHash()
|
||||||
{
|
{
|
||||||
using (TestHostContext hc = new TestHostContext(this))
|
using (TestHostContext hc = new(this))
|
||||||
{
|
{
|
||||||
Tracing trace = hc.GetTrace();
|
Tracing trace = hc.GetTrace();
|
||||||
var externalsHashFile = Path.Combine(TestUtil.GetSrcPath(), $"Misc/contentHash/externals/{BuildConstants.RunnerPackage.PackageName}");
|
var externalsHashFile = Path.Combine(TestUtil.GetSrcPath(), $"Misc/contentHash/externals/{BuildConstants.RunnerPackage.PackageName}");
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
|
|
||||||
private void CleanLogFolder()
|
private void CleanLogFolder()
|
||||||
{
|
{
|
||||||
using (TestHostContext hc = new TestHostContext(this))
|
using (TestHostContext hc = new(this))
|
||||||
{
|
{
|
||||||
//clean test data if any old test forgot
|
//clean test data if any old test forgot
|
||||||
string pagesFolder = Path.Combine(hc.GetDirectory(WellKnownDirectory.Diag), PagingLogger.PagingFolder);
|
string pagesFolder = Path.Combine(hc.GetDirectory(WellKnownDirectory.Diag), PagingLogger.PagingFolder);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user