mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:36:23 +00:00
Compare commits
56 Commits
releases/m
...
v2.318.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e57957c98 | ||
|
|
12506842c0 | ||
|
|
2190396357 | ||
|
|
41bc0da6fe | ||
|
|
2a7f327d93 | ||
|
|
dbcaa7cf3d | ||
|
|
8df87a82b0 | ||
|
|
70746ff593 | ||
|
|
054fc2e046 | ||
|
|
ecb732eaf4 | ||
|
|
3dab1f1fb0 | ||
|
|
8f1c723ba0 | ||
|
|
1e74a8137b | ||
|
|
3f28dd845f | ||
|
|
edfdbb9661 | ||
|
|
00888c10f9 | ||
|
|
84b1bea43e | ||
|
|
ce4d7be00f | ||
|
|
bd7235ef62 | ||
|
|
0f15173045 | ||
|
|
76dc3a28c0 | ||
|
|
c67e7f2813 | ||
|
|
54052b94fb | ||
|
|
f2c05de91c | ||
|
|
18803bdff6 | ||
|
|
04b07b6675 | ||
|
|
dd9fcfc5b2 | ||
|
|
5107c5efb2 | ||
|
|
1b61d78c07 | ||
|
|
2e0eb2c11f | ||
|
|
2d83e1d88f | ||
|
|
4a1e38095b | ||
|
|
f467e9e125 | ||
|
|
77e0bfbb8a | ||
|
|
a52c53955c | ||
|
|
8ebf298bcd | ||
|
|
4b85145661 | ||
|
|
bc8b6e0152 | ||
|
|
82e01c6173 | ||
|
|
93bc1cd918 | ||
|
|
692d910868 | ||
|
|
2c8c941622 | ||
|
|
86d6211c75 | ||
|
|
aa90563cae | ||
|
|
4cb3cb2962 | ||
|
|
d7777fd632 | ||
|
|
d8bce88c4f | ||
|
|
601d3de3f3 | ||
|
|
034c51cd0b | ||
|
|
d296014f99 | ||
|
|
3449d5fa52 | ||
|
|
6603bfb74c | ||
|
|
b19b9462d8 | ||
|
|
3db5c90cc4 | ||
|
|
927b26a364 | ||
|
|
72559572f6 |
@@ -4,13 +4,13 @@
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
||||
"ghcr.io/devcontainers/features/dotnet": {
|
||||
"version": "6.0.418"
|
||||
"version": "8.0.303"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "16"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/sshd:1": {
|
||||
"version": "latest"
|
||||
"version": "latest"
|
||||
}
|
||||
},
|
||||
"customizations": {
|
||||
|
||||
4
.github/workflows/publish-image.yml
vendored
4
.github/workflows/publish-image.yml
vendored
@@ -25,10 +25,12 @@ jobs:
|
||||
- name: Compute image version
|
||||
id: image
|
||||
uses: actions/github-script@v6
|
||||
env:
|
||||
RUNNER_VERSION: ${{ github.event.inputs.runnerVersion }}
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const inputRunnerVersion = "${{ github.event.inputs.runnerVersion }}"
|
||||
const inputRunnerVersion = process.env.RUNNER_VERSION;
|
||||
if (inputRunnerVersion) {
|
||||
console.log(`Using input runner version ${inputRunnerVersion}`)
|
||||
core.setOutput('version', inputRunnerVersion);
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -26,4 +26,5 @@ _dotnetsdk
|
||||
TestResults
|
||||
TestLogs
|
||||
.DS_Store
|
||||
.mono
|
||||
**/*.DotSettings.user
|
||||
@@ -4,16 +4,7 @@
|
||||
|
||||
## Supported Distributions and Versions
|
||||
|
||||
x64
|
||||
- Red Hat Enterprise Linux 7+
|
||||
- CentOS 7+
|
||||
- Oracle Linux 7+
|
||||
- Fedora 29+
|
||||
- Debian 9+
|
||||
- Ubuntu 16.04+
|
||||
- Linux Mint 18+
|
||||
- openSUSE 15+
|
||||
- SUSE Enterprise Linux (SLES) 12 SP2+
|
||||
Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#linux)."
|
||||
|
||||
## Install .Net Core 3.x Linux Dependencies
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
## Supported Versions
|
||||
|
||||
- macOS High Sierra (10.13) and later versions
|
||||
- x64 and arm64 (Apple Silicon)
|
||||
Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#macos)."
|
||||
|
||||
## [More .Net Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/macos-prerequisites?tabs=netcore30)
|
||||
|
||||
@@ -2,11 +2,6 @@
|
||||
|
||||
## Supported Versions
|
||||
|
||||
- Windows 7 64-bit
|
||||
- Windows 8.1 64-bit
|
||||
- Windows 10 64-bit
|
||||
- Windows Server 2012 R2 64-bit
|
||||
- Windows Server 2016 64-bit
|
||||
- Windows Server 2019 64-bit
|
||||
Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#windows)."
|
||||
|
||||
## [More .NET Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore30)
|
||||
|
||||
@@ -4,9 +4,9 @@ FROM mcr.microsoft.com/dotnet/runtime-deps:6.0-jammy as build
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ARG RUNNER_VERSION
|
||||
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.5.1
|
||||
ARG DOCKER_VERSION=25.0.2
|
||||
ARG BUILDX_VERSION=0.12.1
|
||||
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.6.1
|
||||
ARG DOCKER_VERSION=27.1.1
|
||||
ARG BUILDX_VERSION=0.16.2
|
||||
|
||||
RUN apt update -y && apt install curl unzip -y
|
||||
|
||||
@@ -39,12 +39,15 @@ ENV RUNNER_MANUALLY_TRAP_SIG=1
|
||||
ENV ACTIONS_RUNNER_PRINT_LOG_TO_STDOUT=1
|
||||
ENV ImageOS=ubuntu22
|
||||
|
||||
RUN apt-get update -y \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
sudo \
|
||||
lsb-release \
|
||||
# 'gpg-agent' and 'software-properties-common' are needed for the 'add-apt-repository' command that follows
|
||||
RUN apt update -y \
|
||||
&& apt install -y --no-install-recommends sudo lsb-release gpg-agent software-properties-common \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Configure git-core/ppa based on guidance here: https://git-scm.com/download/linux
|
||||
RUN add-apt-repository ppa:git-core/ppa \
|
||||
&& apt update -y
|
||||
|
||||
RUN adduser --disabled-password --gecos "" --uid 1001 runner \
|
||||
&& groupadd docker --gid 123 \
|
||||
&& usermod -aG sudo runner \
|
||||
|
||||
@@ -1,40 +1,30 @@
|
||||
## What's Changed
|
||||
* Bump docker and buildx to the latest version by @diogotorres97 in https://github.com/actions/runner/pull/3100
|
||||
* Revert "Bump container hook version to 0.5.0 in runner image (#3003)" by @TingluoHuang in https://github.com/actions/runner/pull/3101
|
||||
* Make embedded timeline record has same order as its parent record. by @TingluoHuang in https://github.com/actions/runner/pull/3113
|
||||
* Fix release workflow. by @TingluoHuang in https://github.com/actions/runner/pull/3102
|
||||
* Add user-agent to all http clients using RawClientHttpRequestSettings. by @TingluoHuang in https://github.com/actions/runner/pull/3115
|
||||
* Fix JobDispatcher crash during force cancellation. by @TingluoHuang in https://github.com/actions/runner/pull/3118
|
||||
* Implement Broker Redirects for Session and Messages by @luketomlinson in https://github.com/actions/runner/pull/3103
|
||||
* Only keep 1 older version runner around after self-upgrade. by @TingluoHuang in https://github.com/actions/runner/pull/3122
|
||||
* Upgrade `docker` from `24.0.7` to `24.0.8` by @MPV in https://github.com/actions/runner/pull/3124
|
||||
* Upload the diagnostic logs to the Results Service by @jtamsut in https://github.com/actions/runner/pull/3114
|
||||
* Upgrade `docker` from `24.0.8` to `24.0.9` by @MPV in https://github.com/actions/runner/pull/3126
|
||||
* Make sure to drain the upload queue before clean temp directory by @yacaovsnc in https://github.com/actions/runner/pull/3125
|
||||
* Specify `Content-Type` for BlockBlob upload by @bethanyj28 in https://github.com/actions/runner/pull/3119
|
||||
* Improve error report for invalid action.yml by @jsoref in https://github.com/actions/runner/pull/3106
|
||||
* Add sshd to .devcontainer.json by @pje in https://github.com/actions/runner/pull/3079
|
||||
* Resolve CVE-2024-21626 by @luka5 in https://github.com/actions/runner/pull/3123
|
||||
* Handle ForceTokenRefresh message by @luketomlinson in https://github.com/actions/runner/pull/3133
|
||||
* Bump hook version to 0.5.1 by @nikola-jokic in https://github.com/actions/runner/pull/3129
|
||||
|
||||
## New Contributors
|
||||
* @diogotorres97 made their first contribution in https://github.com/actions/runner/pull/3100
|
||||
* @MPV made their first contribution in https://github.com/actions/runner/pull/3124
|
||||
* @jtamsut made their first contribution in https://github.com/actions/runner/pull/3114
|
||||
* @luka5 made their first contribution in https://github.com/actions/runner/pull/3123
|
||||
- Update Docker to v27.1.1 by @TingluoHuang in https://github.com/actions/runner/pull/3401
|
||||
- Upgrade dotnet sdk to v8.0.303 in https://github.com/actions/runner/pull/3388
|
||||
- Rephrase node20 warning by @rentziass in https://github.com/actions/runner/pull/3376
|
||||
- Bump hook version to 0.6.1 by @nikola-jokic in https://github.com/actions/runner/pull/3350
|
||||
- Backoff to avoid excessive retries to Run Service in a duration by @ericsciple in https://github.com/actions/runner/pull/3354
|
||||
- Bump System.Security.Cryptography.Pkcs from 5.0.0 to 8.0.0 in /src in https://github.com/actions/runner/pull/3347
|
||||
- Upgrade dotnet sdk to v8.0.302 in https://github.com/actions/runner/pull/3346
|
||||
- Bump runner to dotnet 8 by @TingluoHuang in https://github.com/actions/runner/pull/3345
|
||||
- Pass runner version as environment variable in workflow by @joshmgross in https://github.com/actions/runner/pull/3318
|
||||
- Make sure we mask secrets when reporting telemetry by @TingluoHuang in https://github.com/actions/runner/pull/3315
|
||||
- Bump docker version and docker buildx version by @int128 in https://github.com/actions/runner/pull/3277
|
||||
|
||||
**Full Changelog**: https://github.com/actions/runner/compare/v2.312.0...v2.313.0
|
||||
**Full Changelog**: https://github.com/actions/runner/compare/v2.317.0...v2.318.0
|
||||
|
||||
_Note: Actions Runner follows a progressive release policy, so the latest release might not be available to your enterprise, organization, or repository yet.
|
||||
To confirm which version of the Actions Runner you should expect, please view the download instructions for your enterprise, organization, or repository.
|
||||
See https://docs.github.com/en/enterprise-cloud@latest/actions/hosting-your-own-runners/adding-self-hosted-runners_
|
||||
|
||||
## Windows x64
|
||||
|
||||
We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows.
|
||||
|
||||
The following snipped needs to be run on `powershell`:
|
||||
``` powershell
|
||||
|
||||
```powershell
|
||||
# Create a folder under the drive root
|
||||
mkdir \actions-runner ; cd \actions-runner
|
||||
# Download the latest runner package
|
||||
@@ -45,12 +35,14 @@ Add-Type -AssemblyName System.IO.Compression.FileSystem ;
|
||||
```
|
||||
|
||||
## [Pre-release] Windows arm64
|
||||
|
||||
**Warning:** Windows arm64 runners are currently in preview status and use [unofficial versions of nodejs](https://unofficial-builds.nodejs.org/). They are not intended for production workflows.
|
||||
|
||||
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.
|
||||
|
||||
The following snipped needs to be run on `powershell`:
|
||||
``` powershell
|
||||
|
||||
```powershell
|
||||
# Create a folder under the drive root
|
||||
mkdir \actions-runner ; cd \actions-runner
|
||||
# Download the latest runner package
|
||||
@@ -62,7 +54,7 @@ Add-Type -AssemblyName System.IO.Compression.FileSystem ;
|
||||
|
||||
## OSX x64
|
||||
|
||||
``` bash
|
||||
```bash
|
||||
# Create a folder
|
||||
mkdir actions-runner && cd actions-runner
|
||||
# Download the latest runner package
|
||||
@@ -73,7 +65,7 @@ tar xzf ./actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz
|
||||
|
||||
## OSX arm64 (Apple silicon)
|
||||
|
||||
``` bash
|
||||
```bash
|
||||
# Create a folder
|
||||
mkdir actions-runner && cd actions-runner
|
||||
# Download the latest runner package
|
||||
@@ -84,7 +76,7 @@ tar xzf ./actions-runner-osx-arm64-<RUNNER_VERSION>.tar.gz
|
||||
|
||||
## Linux x64
|
||||
|
||||
``` bash
|
||||
```bash
|
||||
# Create a folder
|
||||
mkdir actions-runner && cd actions-runner
|
||||
# Download the latest runner package
|
||||
@@ -95,7 +87,7 @@ tar xzf ./actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz
|
||||
|
||||
## Linux arm64
|
||||
|
||||
``` bash
|
||||
```bash
|
||||
# Create a folder
|
||||
mkdir actions-runner && cd actions-runner
|
||||
# Download the latest runner package
|
||||
@@ -106,7 +98,7 @@ tar xzf ./actions-runner-linux-arm64-<RUNNER_VERSION>.tar.gz
|
||||
|
||||
## Linux arm
|
||||
|
||||
``` bash
|
||||
```bash
|
||||
# Create a folder
|
||||
mkdir actions-runner && cd actions-runner
|
||||
# Download the latest runner package
|
||||
@@ -116,6 +108,7 @@ tar xzf ./actions-runner-linux-arm-<RUNNER_VERSION>.tar.gz
|
||||
```
|
||||
|
||||
## Using your self hosted runner
|
||||
|
||||
For additional details about configuring, running, or shutting down the runner please check out our [product docs.](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/adding-self-hosted-runners)
|
||||
|
||||
## SHA-256 Checksums
|
||||
|
||||
@@ -1 +1 @@
|
||||
<Update to ./src/runnerversion when creating release>
|
||||
2.318.0
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
54d95a44d118dba852395991224a6b9c1abe916858c87138656f80c619e85331
|
||||
@@ -1 +0,0 @@
|
||||
68015af17f06a824fa478e62ae7393766ce627fd5599ab916432a14656a19a52
|
||||
@@ -1 +0,0 @@
|
||||
a2628119ca419cb54e279103ffae7986cdbd0814d57c73ff0dc74c38be08b9ae
|
||||
@@ -1 +0,0 @@
|
||||
de71ca09ead807e1a2ce9df0a5b23eb7690cb71fff51169a77e4c3992be53dda
|
||||
@@ -1 +0,0 @@
|
||||
d009e05e6b26d614d65be736a15d1bd151932121c16a9ff1b986deadecc982b9
|
||||
@@ -1 +0,0 @@
|
||||
f730db39c2305800b4653795360ba9c10c68f384a46b85d808f1f9f0ed3c42e4
|
||||
@@ -1 +0,0 @@
|
||||
a35b5722375490e9473cdcccb5e18b41eba3dbf4344fe31abc9821e21f18ea5a
|
||||
1
src/Misc/contentHash/externals/linux-arm
vendored
1
src/Misc/contentHash/externals/linux-arm
vendored
@@ -1 +0,0 @@
|
||||
4bf3e1af0d482af1b2eaf9f08250248a8c1aea8ec20a3c5be116d58cdd930009
|
||||
1
src/Misc/contentHash/externals/linux-arm64
vendored
1
src/Misc/contentHash/externals/linux-arm64
vendored
@@ -1 +0,0 @@
|
||||
ec1719a8cb4d8687328aa64f4aa7c4e3498a715d8939117874782e3e6e63a14b
|
||||
1
src/Misc/contentHash/externals/linux-x64
vendored
1
src/Misc/contentHash/externals/linux-x64
vendored
@@ -1 +0,0 @@
|
||||
50538de29f173bb73f708c4ed2c8328a62b8795829b97b2a6cb57197e2305287
|
||||
1
src/Misc/contentHash/externals/osx-arm64
vendored
1
src/Misc/contentHash/externals/osx-arm64
vendored
@@ -1 +0,0 @@
|
||||
a0a96cbb7593643b69e669bf14d7b29b7f27800b3a00bb3305aebe041456c701
|
||||
1
src/Misc/contentHash/externals/osx-x64
vendored
1
src/Misc/contentHash/externals/osx-x64
vendored
@@ -1 +0,0 @@
|
||||
6255b22692779467047ecebd60ad46984866d75cdfe10421d593a7b51d620b09
|
||||
1
src/Misc/contentHash/externals/win-arm64
vendored
1
src/Misc/contentHash/externals/win-arm64
vendored
@@ -1 +0,0 @@
|
||||
6ff1abd055dc35bfbf06f75c2f08908f660346f66ad1d8f81c910068e9ba029d
|
||||
1
src/Misc/contentHash/externals/win-x64
vendored
1
src/Misc/contentHash/externals/win-x64
vendored
@@ -1 +0,0 @@
|
||||
433a6d748742d12abd20dc2a79b62ac3d9718ae47ef26f8e84dc8c180eea3659
|
||||
@@ -5,10 +5,11 @@ PRECACHE=$2
|
||||
NODE_URL=https://nodejs.org/dist
|
||||
UNOFFICIAL_NODE_URL=https://unofficial-builds.nodejs.org/download/release
|
||||
NODE_ALPINE_URL=https://github.com/actions/alpine_nodejs/releases/download
|
||||
# When you update Node versions you must also create a new release of alpine_nodejs at that updated version.
|
||||
# Follow the instructions here: https://github.com/actions/alpine_nodejs?tab=readme-ov-file#getting-started
|
||||
NODE16_VERSION="16.20.2"
|
||||
NODE20_VERSION="20.8.1"
|
||||
# used only for win-arm64, remove node16 unofficial version when official version is available
|
||||
NODE16_UNOFFICIAL_VERSION="16.20.0"
|
||||
NODE20_VERSION="20.13.1"
|
||||
NODE16_UNOFFICIAL_VERSION="16.20.0" # used only for win-arm64, remove node16 unofficial version when official version is available
|
||||
|
||||
get_abs_path() {
|
||||
# exploits the fact that pwd will print abs path when no args
|
||||
|
||||
@@ -114,6 +114,11 @@ var runService = function () {
|
||||
);
|
||||
stopping = true;
|
||||
}
|
||||
} else if (code === 5) {
|
||||
console.log(
|
||||
"Runner listener exit with Session Conflict error, stop the service, no retry needed."
|
||||
);
|
||||
stopping = true;
|
||||
} else {
|
||||
var messagePrefix = "Runner listener exit with undefined return code";
|
||||
unknownFailureRetryCount++;
|
||||
|
||||
@@ -49,5 +49,10 @@ if %ERRORLEVEL% EQU 4 (
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if %ERRORLEVEL% EQU 5 (
|
||||
echo "Runner listener exit with Session Conflict error, stop the service, no retry needed."
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
echo "Exiting after unknown error code: %ERRORLEVEL%"
|
||||
exit /b 0
|
||||
@@ -70,6 +70,9 @@ elif [[ $returnCode == 4 ]]; then
|
||||
"$DIR"/safe_sleep.sh 1
|
||||
done
|
||||
exit 2
|
||||
elif [[ $returnCode == 5 ]]; then
|
||||
echo "Runner listener exit with Session Conflict error, stop the service, no retry needed."
|
||||
exit 0
|
||||
else
|
||||
echo "Exiting with unknown error code: ${returnCode}"
|
||||
exit 0
|
||||
|
||||
@@ -38,7 +38,7 @@ runWithManualTrap() {
|
||||
cp -f "$DIR"/run-helper.sh.template "$DIR"/run-helper.sh
|
||||
"$DIR"/run-helper.sh $* &
|
||||
PID=$!
|
||||
wait -f $PID
|
||||
wait $PID
|
||||
returnCode=$?
|
||||
if [[ $returnCode -eq 2 ]]; then
|
||||
echo "Restarting runner..."
|
||||
@@ -84,4 +84,4 @@ if [[ -z "$RUNNER_MANUALLY_TRAP_SIG" ]]; then
|
||||
run $*
|
||||
else
|
||||
runWithManualTrap $*
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -20,12 +20,12 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
private bool _hasConnection;
|
||||
private VssConnection _connection;
|
||||
private TaskAgentHttpClient _taskAgentClient;
|
||||
private ActionsRunServerHttpClient _actionsRunServerClient;
|
||||
|
||||
public async Task ConnectAsync(Uri serverUrl, VssCredentials credentials)
|
||||
{
|
||||
_connection = await EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(100));
|
||||
_taskAgentClient = _connection.GetClient<TaskAgentHttpClient>();
|
||||
_actionsRunServerClient = _connection.GetClient<ActionsRunServerHttpClient>();
|
||||
_hasConnection = true;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace GitHub.Runner.Common
|
||||
CheckConnection();
|
||||
var jobMessage = RetryRequest<AgentJobRequestMessage>(async () =>
|
||||
{
|
||||
return await _taskAgentClient.GetJobMessageAsync(id, cancellationToken);
|
||||
return await _actionsRunServerClient.GetJobMessageAsync(id, cancellationToken);
|
||||
}, cancellationToken);
|
||||
|
||||
return jobMessage;
|
||||
|
||||
@@ -21,6 +21,10 @@ namespace GitHub.Runner.Common
|
||||
Task DeleteSessionAsync(CancellationToken cancellationToken);
|
||||
|
||||
Task<TaskAgentMessage> GetRunnerMessageAsync(Guid? sessionId, TaskAgentStatus status, string version, string os, string architecture, bool disableUpdate, CancellationToken token);
|
||||
|
||||
Task UpdateConnectionIfNeeded(Uri serverUri, VssCredentials credentials);
|
||||
|
||||
Task ForceRefreshConnection(VssCredentials credentials);
|
||||
}
|
||||
|
||||
public sealed class BrokerServer : RunnerService, IBrokerServer
|
||||
@@ -59,7 +63,8 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
CheckConnection();
|
||||
var brokerSession = RetryRequest<TaskAgentMessage>(
|
||||
async () => await _brokerHttpClient.GetRunnerMessageAsync(sessionId, version, status, os, architecture, disableUpdate, cancellationToken), cancellationToken);
|
||||
async () => await _brokerHttpClient.GetRunnerMessageAsync(sessionId, version, status, os, architecture, disableUpdate, cancellationToken), cancellationToken, shouldRetry: ShouldRetryException);
|
||||
|
||||
|
||||
return brokerSession;
|
||||
}
|
||||
@@ -69,5 +74,30 @@ namespace GitHub.Runner.Common
|
||||
CheckConnection();
|
||||
await _brokerHttpClient.DeleteSessionAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public Task UpdateConnectionIfNeeded(Uri serverUri, VssCredentials credentials)
|
||||
{
|
||||
if (_brokerUri != serverUri || !_hasConnection)
|
||||
{
|
||||
return ConnectAsync(serverUri, credentials);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task ForceRefreshConnection(VssCredentials credentials)
|
||||
{
|
||||
return ConnectAsync(_brokerUri, credentials);
|
||||
}
|
||||
|
||||
public bool ShouldRetryException(Exception ex)
|
||||
{
|
||||
if (ex is AccessDeniedException ade && ade.ErrorCode == 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +153,7 @@ namespace GitHub.Runner.Common
|
||||
public const int RetryableError = 2;
|
||||
public const int RunnerUpdating = 3;
|
||||
public const int RunOnceRunnerUpdating = 4;
|
||||
public const int SessionConflict = 5;
|
||||
}
|
||||
|
||||
public static class Features
|
||||
@@ -180,6 +181,9 @@ namespace GitHub.Runner.Common
|
||||
public static readonly string DeprecatedNodeVersion = "node16";
|
||||
public static readonly string EnforcedNode12DetectedAfterEndOfLife = "The following actions uses node12 which is deprecated and will be forced to run on node16: {0}. For more info: https://github.blog/changelog/2023-06-13-github-actions-all-actions-will-run-on-node16-instead-of-node12-by-default/";
|
||||
public static readonly string EnforcedNode12DetectedAfterEndOfLifeEnvVariable = "Node16ForceActionsWarnings";
|
||||
public static readonly string EnforcedNode16DetectedAfterEndOfLife = "The following actions use a deprecated Node.js version and will be forced to run on node20: {0}. For more info: https://github.blog/changelog/2024-03-07-github-actions-all-actions-will-run-on-node20-instead-of-node16-by-default/";
|
||||
public static readonly string EnforcedNode16DetectedAfterEndOfLifeEnvVariable = "Node20ForceActionsWarnings";
|
||||
|
||||
}
|
||||
|
||||
public static class RunnerEvent
|
||||
@@ -251,6 +255,7 @@ namespace GitHub.Runner.Common
|
||||
public static readonly string RunnerDebug = "ACTIONS_RUNNER_DEBUG";
|
||||
public static readonly string StepDebug = "ACTIONS_STEP_DEBUG";
|
||||
public static readonly string AllowActionsUseUnsecureNodeVersion = "ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION";
|
||||
public static readonly string ManualForceActionsToNode20 = "FORCE_JAVASCRIPT_ACTIONS_TO_NODE20";
|
||||
}
|
||||
|
||||
public static class Agent
|
||||
@@ -262,6 +267,7 @@ namespace GitHub.Runner.Common
|
||||
public static readonly string ForcedActionsNodeVersion = "ACTIONS_RUNNER_FORCE_ACTIONS_NODE_VERSION";
|
||||
public static readonly string PrintLogToStdout = "ACTIONS_RUNNER_PRINT_LOG_TO_STDOUT";
|
||||
public static readonly string ActionArchiveCacheDirectory = "ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE";
|
||||
public static readonly string ManualForceActionsToNode20 = "FORCE_JAVASCRIPT_ACTIONS_TO_NODE20";
|
||||
}
|
||||
|
||||
public static class System
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace GitHub.Runner.Common
|
||||
TaskCompletionSource<int> JobRecordUpdated { get; }
|
||||
event EventHandler<ThrottlingEventArgs> JobServerQueueThrottling;
|
||||
Task ShutdownAsync();
|
||||
void Start(Pipelines.AgentJobRequestMessage jobRequest, bool resultsServiceOnly = false, bool enableTelemetry = false);
|
||||
void Start(Pipelines.AgentJobRequestMessage jobRequest, bool resultsServiceOnly = false);
|
||||
void QueueWebConsoleLine(Guid stepRecordId, string line, long? lineNumber = null);
|
||||
void QueueFileUpload(Guid timelineId, Guid timelineRecordId, string type, string name, string path, bool deleteSource);
|
||||
void QueueResultsUpload(Guid timelineRecordId, string name, string path, string type, bool deleteSource, bool finalize, bool firstBlock, long totalLines);
|
||||
@@ -74,6 +74,7 @@ namespace GitHub.Runner.Common
|
||||
private readonly List<JobTelemetry> _jobTelemetries = new();
|
||||
private bool _queueInProcess = false;
|
||||
private bool _resultsServiceOnly = false;
|
||||
private int _resultsServiceExceptionsCount = 0;
|
||||
private Stopwatch _resultsUploadTimer = new();
|
||||
private Stopwatch _actionsUploadTimer = new();
|
||||
|
||||
@@ -104,11 +105,10 @@ namespace GitHub.Runner.Common
|
||||
_resultsServer = hostContext.GetService<IResultsServer>();
|
||||
}
|
||||
|
||||
public void Start(Pipelines.AgentJobRequestMessage jobRequest, bool resultsServiceOnly = false, bool enableTelemetry = false)
|
||||
public void Start(Pipelines.AgentJobRequestMessage jobRequest, bool resultsServiceOnly = false)
|
||||
{
|
||||
Trace.Entering();
|
||||
_resultsServiceOnly = resultsServiceOnly;
|
||||
_enableTelemetry = enableTelemetry;
|
||||
|
||||
var serviceEndPoint = jobRequest.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
@@ -139,6 +139,12 @@ namespace GitHub.Runner.Common
|
||||
_resultsClientInitiated = true;
|
||||
}
|
||||
|
||||
// Enable telemetry if we have both results service and actions service
|
||||
if (_resultsClientInitiated && !_resultsServiceOnly)
|
||||
{
|
||||
_enableTelemetry = true;
|
||||
}
|
||||
|
||||
if (_queueInProcess)
|
||||
{
|
||||
Trace.Info("No-opt, all queue process tasks are running.");
|
||||
@@ -574,9 +580,9 @@ namespace GitHub.Runner.Common
|
||||
Trace.Info("Catch exception during file upload to results, keep going since the process is best effort.");
|
||||
Trace.Error(ex);
|
||||
errorCount++;
|
||||
|
||||
_resultsServiceExceptionsCount++;
|
||||
// If we hit any exceptions uploading to Results, let's skip any additional uploads to Results unless Results is serving logs
|
||||
if (!_resultsServiceOnly)
|
||||
if (!_resultsServiceOnly && _resultsServiceExceptionsCount > 3)
|
||||
{
|
||||
_resultsClientInitiated = false;
|
||||
SendResultsTelemetry(ex);
|
||||
@@ -607,7 +613,7 @@ namespace GitHub.Runner.Common
|
||||
|
||||
private void SendResultsTelemetry(Exception ex)
|
||||
{
|
||||
var issue = new Issue() { Type = IssueType.Warning, Message = $"Caught exception with results. {ex.Message}" };
|
||||
var issue = new Issue() { Type = IssueType.Warning, Message = $"Caught exception with results. {HostContext.SecretMasker.MaskSecrets(ex.Message)}" };
|
||||
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.ResultsUploadFailure;
|
||||
|
||||
var telemetryRecord = new TimelineRecord()
|
||||
@@ -703,7 +709,9 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
Trace.Info("Catch exception during update steps, skip update Results.");
|
||||
Trace.Error(e);
|
||||
if (!_resultsServiceOnly)
|
||||
_resultsServiceExceptionsCount++;
|
||||
// If we hit any exceptions uploading to Results, let's skip any additional uploads to Results unless Results is serving logs
|
||||
if (!_resultsServiceOnly && _resultsServiceExceptionsCount > 3)
|
||||
{
|
||||
_resultsClientInitiated = false;
|
||||
SendResultsTelemetry(e);
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Threading.Tasks;
|
||||
using GitHub.Actions.RunService.WebApi;
|
||||
using GitHub.DistributedTask.Pipelines;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Services.Common;
|
||||
using Sdk.RSWebApi.Contracts;
|
||||
@@ -60,8 +61,11 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
CheckConnection();
|
||||
return RetryRequest<AgentJobRequestMessage>(
|
||||
async () => await _runServiceHttpClient.GetJobMessageAsync(requestUri, id, cancellationToken), cancellationToken,
|
||||
shouldRetry: ex => ex is not TaskOrchestrationJobAlreadyAcquiredException);
|
||||
async () => await _runServiceHttpClient.GetJobMessageAsync(requestUri, id, VarUtil.OS, cancellationToken), cancellationToken,
|
||||
shouldRetry: ex =>
|
||||
ex is not TaskOrchestrationJobNotFoundException && // HTTP status 404
|
||||
ex is not TaskOrchestrationJobAlreadyAcquiredException && // HTTP status 409
|
||||
ex is not TaskOrchestrationJobUnprocessableException); // HTTP status 422
|
||||
}
|
||||
|
||||
public Task CompleteJobAsync(
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<OutputType>Library</OutputType>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64;win-arm64</RuntimeIdentifiers>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<NoWarn>NU1701;NU1603</NoWarn>
|
||||
<NoWarn>NU1701;NU1603;SYSLIB0050;SYSLIB0051</NoWarn>
|
||||
<Version>$(Version)</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.4.0" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="4.4.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace GitHub.Runner.Listener
|
||||
_brokerServer = HostContext.GetService<IBrokerServer>();
|
||||
}
|
||||
|
||||
public async Task<Boolean> CreateSessionAsync(CancellationToken token)
|
||||
public async Task<CreateSessionResult> CreateSessionAsync(CancellationToken token)
|
||||
{
|
||||
Trace.Entering();
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace GitHub.Runner.Listener
|
||||
encounteringError = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return CreateSessionResult.Success;
|
||||
}
|
||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||
{
|
||||
@@ -123,7 +123,7 @@ namespace GitHub.Runner.Listener
|
||||
if (string.Equals(vssOAuthEx.Error, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_term.WriteError("Failed to create a session. The runner registration has been deleted from the server, please re-configure. Runner registrations are automatically deleted for runners that have not connected to the service recently.");
|
||||
return false;
|
||||
return CreateSessionResult.Failure;
|
||||
}
|
||||
|
||||
// Check whether we get 401 because the runner registration already removed by the service.
|
||||
@@ -134,14 +134,18 @@ namespace GitHub.Runner.Listener
|
||||
if (string.Equals(authError, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_term.WriteError("Failed to create a session. The runner registration has been deleted from the server, please re-configure. Runner registrations are automatically deleted for runners that have not connected to the service recently.");
|
||||
return false;
|
||||
return CreateSessionResult.Failure;
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsSessionCreationExceptionRetriable(ex))
|
||||
{
|
||||
_term.WriteError($"Failed to create session. {ex.Message}");
|
||||
return false;
|
||||
if (ex is TaskAgentSessionConflictException)
|
||||
{
|
||||
return CreateSessionResult.SessionConflict;
|
||||
}
|
||||
return CreateSessionResult.Failure;
|
||||
}
|
||||
|
||||
if (!encounteringError) //print the message only on the first error
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#if OS_WINDOWS
|
||||
#pragma warning disable CA1416
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
@@ -84,4 +85,5 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore CA1416
|
||||
#endif
|
||||
|
||||
44
src/Runner.Listener/ErrorThrottler.cs
Normal file
44
src/Runner.Listener/ErrorThrottler.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Services.Common;
|
||||
|
||||
namespace GitHub.Runner.Listener
|
||||
{
|
||||
[ServiceLocator(Default = typeof(ErrorThrottler))]
|
||||
public interface IErrorThrottler : IRunnerService
|
||||
{
|
||||
void Reset();
|
||||
Task IncrementAndWaitAsync(CancellationToken token);
|
||||
}
|
||||
|
||||
public sealed class ErrorThrottler : RunnerService, IErrorThrottler
|
||||
{
|
||||
internal static readonly TimeSpan MinBackoff = TimeSpan.FromSeconds(1);
|
||||
internal static readonly TimeSpan MaxBackoff = TimeSpan.FromMinutes(1);
|
||||
internal static readonly TimeSpan BackoffCoefficient = TimeSpan.FromSeconds(1);
|
||||
private int _count = 0;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
public async Task IncrementAndWaitAsync(CancellationToken token)
|
||||
{
|
||||
if (++_count <= 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TimeSpan backoff = BackoffTimerHelper.GetExponentialBackoff(
|
||||
attempt: _count - 2, // 0-based attempt
|
||||
minBackoff: MinBackoff,
|
||||
maxBackoff: MaxBackoff,
|
||||
deltaBackoff: BackoffCoefficient);
|
||||
Trace.Warning($"Back off {backoff.TotalSeconds} seconds before next attempt. Current consecutive error count: {_count}");
|
||||
await HostContext.Delay(backoff, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ namespace GitHub.Runner.Listener
|
||||
// This implementation of IJobDispatcher is not thread safe.
|
||||
// It is based on the fact that the current design of the runner is a dequeue
|
||||
// and processes one message from the message queue at a time.
|
||||
// In addition, it only executes one job every time,
|
||||
// In addition, it only executes one job every time,
|
||||
// and the server will not send another job while this one is still running.
|
||||
public sealed class JobDispatcher : RunnerService, IJobDispatcher
|
||||
{
|
||||
@@ -546,13 +546,27 @@ namespace GitHub.Runner.Listener
|
||||
Trace.Info($"Return code {returnCode} indicate worker encounter an unhandled exception or app crash, attach worker stdout/stderr to JobRequest result.");
|
||||
|
||||
var jobServer = await InitializeJobServerAsync(systemConnection);
|
||||
await LogWorkerProcessUnhandledException(jobServer, message, detailInfo);
|
||||
|
||||
// Go ahead to finish the job with result 'Failed' if the STDERR from worker is System.IO.IOException, since it typically means we are running out of disk space.
|
||||
if (detailInfo.Contains(typeof(System.IO.IOException).ToString(), StringComparison.OrdinalIgnoreCase))
|
||||
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = detailInfo };
|
||||
unhandledExceptionIssue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.WorkerCrash;
|
||||
switch (jobServer)
|
||||
{
|
||||
Trace.Info($"Finish job with result 'Failed' due to IOException.");
|
||||
await ForceFailJob(jobServer, message, detailInfo);
|
||||
case IJobServer js:
|
||||
{
|
||||
await LogWorkerProcessUnhandledException(js, message, unhandledExceptionIssue);
|
||||
// Go ahead to finish the job with result 'Failed' if the STDERR from worker is System.IO.IOException, since it typically means we are running out of disk space.
|
||||
if (detailInfo.Contains(typeof(System.IO.IOException).ToString(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Trace.Info($"Finish job with result 'Failed' due to IOException.");
|
||||
await ForceFailJob(js, message);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case IRunServer rs:
|
||||
await ForceFailJob(rs, message, unhandledExceptionIssue);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException($"JobServer type '{jobServer.GetType().Name}' is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -644,7 +658,7 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
}
|
||||
|
||||
// wait worker to exit
|
||||
// wait worker to exit
|
||||
// if worker doesn't exit within timeout, then kill worker.
|
||||
completedTask = await Task.WhenAny(workerProcessTask, Task.Delay(-1, workerCancelTimeoutKillToken));
|
||||
|
||||
@@ -1131,86 +1145,65 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
|
||||
// log an error issue to job level timeline record
|
||||
private async Task LogWorkerProcessUnhandledException(IRunnerService server, Pipelines.AgentJobRequestMessage message, string detailInfo)
|
||||
private async Task LogWorkerProcessUnhandledException(IJobServer jobServer, Pipelines.AgentJobRequestMessage message, Issue issue)
|
||||
{
|
||||
if (server is IJobServer jobServer)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
var timeline = await jobServer.GetTimelineAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, CancellationToken.None);
|
||||
ArgUtil.NotNull(timeline, nameof(timeline));
|
||||
var timeline = await jobServer.GetTimelineAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, CancellationToken.None);
|
||||
ArgUtil.NotNull(timeline, nameof(timeline));
|
||||
|
||||
TimelineRecord jobRecord = timeline.Records.FirstOrDefault(x => x.Id == message.JobId && x.RecordType == "Job");
|
||||
ArgUtil.NotNull(jobRecord, nameof(jobRecord));
|
||||
TimelineRecord jobRecord = timeline.Records.FirstOrDefault(x => x.Id == message.JobId && x.RecordType == "Job");
|
||||
ArgUtil.NotNull(jobRecord, nameof(jobRecord));
|
||||
|
||||
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = detailInfo };
|
||||
unhandledExceptionIssue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.WorkerCrash;
|
||||
jobRecord.ErrorCount++;
|
||||
jobRecord.Issues.Add(unhandledExceptionIssue);
|
||||
jobRecord.ErrorCount++;
|
||||
jobRecord.Issues.Add(issue);
|
||||
|
||||
if (message.Variables.TryGetValue("DistributedTask.MarkJobAsFailedOnWorkerCrash", out var markJobAsFailedOnWorkerCrash) &&
|
||||
StringUtil.ConvertToBoolean(markJobAsFailedOnWorkerCrash?.Value))
|
||||
{
|
||||
Trace.Info("Mark the job as failed since the worker crashed");
|
||||
jobRecord.Result = TaskResult.Failed;
|
||||
// mark the job as completed so service will pickup the result
|
||||
jobRecord.State = TimelineRecordState.Completed;
|
||||
}
|
||||
Trace.Info("Mark the job as failed since the worker crashed");
|
||||
jobRecord.Result = TaskResult.Failed;
|
||||
// mark the job as completed so service will pickup the result
|
||||
jobRecord.State = TimelineRecordState.Completed;
|
||||
|
||||
await jobServer.UpdateTimelineRecordsAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, new TimelineRecord[] { jobRecord }, CancellationToken.None);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error("Fail to report unhandled exception from Runner.Worker process");
|
||||
Trace.Error(ex);
|
||||
}
|
||||
await jobServer.UpdateTimelineRecordsAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, new TimelineRecord[] { jobRecord }, CancellationToken.None);
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Info("Job server does not support handling unhandled exception yet, error message: {0}", detailInfo);
|
||||
return;
|
||||
Trace.Error("Fail to report unhandled exception from Runner.Worker process");
|
||||
Trace.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
// raise job completed event to fail the job.
|
||||
private async Task ForceFailJob(IRunnerService server, Pipelines.AgentJobRequestMessage message, string detailInfo)
|
||||
private async Task ForceFailJob(IJobServer jobServer, Pipelines.AgentJobRequestMessage message)
|
||||
{
|
||||
if (server is IJobServer jobServer)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
var jobCompletedEvent = new JobCompletedEvent(message.RequestId, message.JobId, TaskResult.Failed);
|
||||
await jobServer.RaisePlanEventAsync<JobCompletedEvent>(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, jobCompletedEvent, CancellationToken.None);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error("Fail to raise JobCompletedEvent back to service.");
|
||||
Trace.Error(ex);
|
||||
}
|
||||
var jobCompletedEvent = new JobCompletedEvent(message.RequestId, message.JobId, TaskResult.Failed);
|
||||
await jobServer.RaisePlanEventAsync<JobCompletedEvent>(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, jobCompletedEvent, CancellationToken.None);
|
||||
}
|
||||
else if (server is IRunServer runServer)
|
||||
catch (Exception ex)
|
||||
{
|
||||
try
|
||||
{
|
||||
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = detailInfo };
|
||||
var unhandledAnnotation = unhandledExceptionIssue.ToAnnotation();
|
||||
var jobAnnotations = new List<Annotation>();
|
||||
if (unhandledAnnotation.HasValue)
|
||||
{
|
||||
jobAnnotations.Add(unhandledAnnotation.Value);
|
||||
}
|
||||
Trace.Error("Fail to raise JobCompletedEvent back to service.");
|
||||
Trace.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, TaskResult.Failed, outputs: null, stepResults: null, jobAnnotations: jobAnnotations, environmentUrl: null, CancellationToken.None);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error("Fail to raise job completion back to service.");
|
||||
Trace.Error(ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
private async Task ForceFailJob(IRunServer runServer, Pipelines.AgentJobRequestMessage message, Issue issue)
|
||||
{
|
||||
try
|
||||
{
|
||||
throw new NotSupportedException($"Server type {server.GetType().FullName} is not supported.");
|
||||
var annotation = issue.ToAnnotation();
|
||||
var jobAnnotations = new List<Annotation>();
|
||||
if (annotation.HasValue)
|
||||
{
|
||||
jobAnnotations.Add(annotation.Value);
|
||||
}
|
||||
|
||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, TaskResult.Failed, outputs: null, stepResults: null, jobAnnotations: jobAnnotations, environmentUrl: null, CancellationToken.None);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error("Fail to raise job completion back to service.");
|
||||
Trace.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,17 @@ using GitHub.Services.WebApi;
|
||||
|
||||
namespace GitHub.Runner.Listener
|
||||
{
|
||||
public enum CreateSessionResult
|
||||
{
|
||||
Success,
|
||||
Failure,
|
||||
SessionConflict
|
||||
}
|
||||
|
||||
[ServiceLocator(Default = typeof(MessageListener))]
|
||||
public interface IMessageListener : IRunnerService
|
||||
{
|
||||
Task<Boolean> CreateSessionAsync(CancellationToken token);
|
||||
Task<CreateSessionResult> CreateSessionAsync(CancellationToken token);
|
||||
Task DeleteSessionAsync();
|
||||
Task<TaskAgentMessage> GetNextMessageAsync(CancellationToken token);
|
||||
Task DeleteMessageAsync(TaskAgentMessage message);
|
||||
@@ -59,7 +66,7 @@ namespace GitHub.Runner.Listener
|
||||
_brokerServer = hostContext.GetService<IBrokerServer>();
|
||||
}
|
||||
|
||||
public async Task<Boolean> CreateSessionAsync(CancellationToken token)
|
||||
public async Task<CreateSessionResult> CreateSessionAsync(CancellationToken token)
|
||||
{
|
||||
Trace.Entering();
|
||||
|
||||
@@ -109,7 +116,8 @@ namespace GitHub.Runner.Listener
|
||||
if (_session.BrokerMigrationMessage != null)
|
||||
{
|
||||
Trace.Info("Runner session is in migration mode: Creating Broker session with BrokerBaseUrl: {0}", _session.BrokerMigrationMessage.BrokerBaseUrl);
|
||||
await _brokerServer.ConnectAsync(_session.BrokerMigrationMessage.BrokerBaseUrl, _creds);
|
||||
|
||||
await _brokerServer.UpdateConnectionIfNeeded(_session.BrokerMigrationMessage.BrokerBaseUrl, _creds);
|
||||
_session = await _brokerServer.CreateSessionAsync(taskAgentSession, token);
|
||||
_isBrokerSession = true;
|
||||
}
|
||||
@@ -122,7 +130,7 @@ namespace GitHub.Runner.Listener
|
||||
encounteringError = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return CreateSessionResult.Success;
|
||||
}
|
||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||
{
|
||||
@@ -146,7 +154,7 @@ namespace GitHub.Runner.Listener
|
||||
if (string.Equals(vssOAuthEx.Error, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_term.WriteError("Failed to create a session. The runner registration has been deleted from the server, please re-configure. Runner registrations are automatically deleted for runners that have not connected to the service recently.");
|
||||
return false;
|
||||
return CreateSessionResult.Failure;
|
||||
}
|
||||
|
||||
// Check whether we get 401 because the runner registration already removed by the service.
|
||||
@@ -157,14 +165,18 @@ namespace GitHub.Runner.Listener
|
||||
if (string.Equals(authError, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_term.WriteError("Failed to create a session. The runner registration has been deleted from the server, please re-configure. Runner registrations are automatically deleted for runners that have not connected to the service recently.");
|
||||
return false;
|
||||
return CreateSessionResult.Failure;
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsSessionCreationExceptionRetriable(ex))
|
||||
{
|
||||
_term.WriteError($"Failed to create session. {ex.Message}");
|
||||
return false;
|
||||
if (ex is TaskAgentSessionConflictException)
|
||||
{
|
||||
return CreateSessionResult.SessionConflict;
|
||||
}
|
||||
return CreateSessionResult.Failure;
|
||||
}
|
||||
|
||||
if (!encounteringError) //print the message only on the first error
|
||||
@@ -187,12 +199,12 @@ namespace GitHub.Runner.Listener
|
||||
{
|
||||
using (var ts = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
|
||||
{
|
||||
await _runnerServer.DeleteAgentSessionAsync(_settings.PoolId, _session.SessionId, ts.Token);
|
||||
|
||||
if (_isBrokerSession)
|
||||
{
|
||||
await _brokerServer.DeleteSessionAsync(ts.Token);
|
||||
return;
|
||||
}
|
||||
await _runnerServer.DeleteAgentSessionAsync(_settings.PoolId, _session.SessionId, ts.Token);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -204,19 +216,17 @@ namespace GitHub.Runner.Listener
|
||||
|
||||
public void OnJobStatus(object sender, JobStatusEventArgs e)
|
||||
{
|
||||
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("USE_BROKER_FLOW")))
|
||||
Trace.Info("Received job status event. JobState: {0}", e.Status);
|
||||
runnerStatus = e.Status;
|
||||
try
|
||||
{
|
||||
Trace.Info("Received job status event. JobState: {0}", e.Status);
|
||||
runnerStatus = e.Status;
|
||||
try
|
||||
{
|
||||
_getMessagesTokenSource?.Cancel();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
Trace.Info("_getMessagesTokenSource is already disposed.");
|
||||
}
|
||||
_getMessagesTokenSource?.Cancel();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
Trace.Info("_getMessagesTokenSource is already disposed.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async Task<TaskAgentMessage> GetNextMessageAsync(CancellationToken token)
|
||||
@@ -226,6 +236,7 @@ namespace GitHub.Runner.Listener
|
||||
ArgUtil.NotNull(_settings, nameof(_settings));
|
||||
bool encounteringError = false;
|
||||
int continuousError = 0;
|
||||
int continuousEmptyMessage = 0;
|
||||
string errorMessage = string.Empty;
|
||||
Stopwatch heartbeat = new();
|
||||
heartbeat.Restart();
|
||||
@@ -256,7 +267,7 @@ namespace GitHub.Runner.Listener
|
||||
|
||||
var migrationMessage = JsonUtility.FromString<BrokerMigrationMessage>(message.Body);
|
||||
|
||||
await _brokerServer.ConnectAsync(migrationMessage.BrokerBaseUrl, _creds);
|
||||
await _brokerServer.UpdateConnectionIfNeeded(migrationMessage.BrokerBaseUrl, _creds);
|
||||
message = await _brokerServer.GetRunnerMessageAsync(_session.SessionId,
|
||||
runnerStatus,
|
||||
BuildConstants.RunnerPackage.Version,
|
||||
@@ -304,7 +315,7 @@ namespace GitHub.Runner.Listener
|
||||
Trace.Error(ex);
|
||||
|
||||
// don't retry if SkipSessionRecover = true, DT service will delete agent session to stop agent from taking more jobs.
|
||||
if (ex is TaskAgentSessionExpiredException && !_settings.SkipSessionRecover && await CreateSessionAsync(token))
|
||||
if (ex is TaskAgentSessionExpiredException && !_settings.SkipSessionRecover && (await CreateSessionAsync(token) == CreateSessionResult.Success))
|
||||
{
|
||||
Trace.Info($"{nameof(TaskAgentSessionExpiredException)} received, recovered by recreate session.");
|
||||
}
|
||||
@@ -349,16 +360,27 @@ namespace GitHub.Runner.Listener
|
||||
|
||||
if (message == null)
|
||||
{
|
||||
continuousEmptyMessage++;
|
||||
if (heartbeat.Elapsed > TimeSpan.FromMinutes(30))
|
||||
{
|
||||
Trace.Info($"No message retrieved from session '{_session.SessionId}' within last 30 minutes.");
|
||||
heartbeat.Restart();
|
||||
continuousEmptyMessage = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace.Verbose($"No message retrieved from session '{_session.SessionId}'.");
|
||||
}
|
||||
|
||||
if (continuousEmptyMessage > 50)
|
||||
{
|
||||
// retried more than 50 times in less than 30mins and still getting empty message
|
||||
// something is not right on the service side, backoff for 15-30s before retry
|
||||
_getNextMessageRetryInterval = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(30), _getNextMessageRetryInterval);
|
||||
Trace.Info("Sleeping for {0} seconds before retrying.", _getNextMessageRetryInterval.TotalSeconds);
|
||||
await HostContext.Delay(_getNextMessageRetryInterval, token);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -384,6 +406,7 @@ namespace GitHub.Runner.Listener
|
||||
public async Task RefreshListenerTokenAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await _runnerServer.RefreshConnectionAsync(RunnerConnectionType.MessageQueue, TimeSpan.FromSeconds(60));
|
||||
await _brokerServer.ForceRefreshConnection(_creds);
|
||||
}
|
||||
|
||||
private TaskAgentMessage DecryptMessage(TaskAgentMessage message)
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64;win-arm64</RuntimeIdentifiers>
|
||||
<SelfContained>true</SelfContained>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<NoWarn>NU1701;NU1603</NoWarn>
|
||||
<NoWarn>NU1701;NU1603;SYSLIB0050;SYSLIB0051</NoWarn>
|
||||
<Version>$(Version)</Version>
|
||||
<PredefinedCulturesOnly>false</PredefinedCulturesOnly>
|
||||
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
|
||||
@@ -18,11 +19,11 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="4.4.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="4.4.0" />
|
||||
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
|
||||
@@ -32,10 +32,25 @@ namespace GitHub.Runner.Listener
|
||||
private bool _inConfigStage;
|
||||
private ManualResetEvent _completedCommand = new(false);
|
||||
|
||||
// <summary>
|
||||
// Helps avoid excessive calls to Run Service when encountering non-retriable errors from /acquirejob.
|
||||
// Normally we rely on the HTTP clients to back off between retry attempts. However, acquiring a job
|
||||
// involves calls to both Run Serivce and Broker. And Run Service and Broker communicate with each other
|
||||
// in an async fashion.
|
||||
//
|
||||
// When Run Service encounters a non-retriable error, it sends an async message to Broker. The runner will,
|
||||
// however, immediately call Broker to get the next message. If the async event from Run Service to Broker
|
||||
// has not yet been processed, the next message from Broker may be the same job message.
|
||||
//
|
||||
// The error throttler helps us back off when encountering successive, non-retriable errors from /acquirejob.
|
||||
// </summary>
|
||||
private IErrorThrottler _acquireJobThrottler;
|
||||
|
||||
public override void Initialize(IHostContext hostContext)
|
||||
{
|
||||
base.Initialize(hostContext);
|
||||
_term = HostContext.GetService<ITerminal>();
|
||||
_acquireJobThrottler = HostContext.CreateService<IErrorThrottler>();
|
||||
}
|
||||
|
||||
public async Task<int> ExecuteCommand(CommandSettings command)
|
||||
@@ -213,10 +228,12 @@ namespace GitHub.Runner.Listener
|
||||
var configFile = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), config.Key);
|
||||
var configContent = Convert.FromBase64String(config.Value);
|
||||
#if OS_WINDOWS
|
||||
#pragma warning disable CA1416
|
||||
if (configFile == HostContext.GetConfigFile(WellKnownConfigFile.RSACredentials))
|
||||
{
|
||||
configContent = ProtectedData.Protect(configContent, null, DataProtectionScope.LocalMachine);
|
||||
}
|
||||
#pragma warning restore CA1416
|
||||
#endif
|
||||
File.WriteAllBytes(configFile, configContent);
|
||||
File.SetAttributes(configFile, File.GetAttributes(configFile) | FileAttributes.Hidden);
|
||||
@@ -359,7 +376,12 @@ namespace GitHub.Runner.Listener
|
||||
{
|
||||
Trace.Info(nameof(RunAsync));
|
||||
_listener = GetMesageListener(settings);
|
||||
if (!await _listener.CreateSessionAsync(HostContext.RunnerShutdownToken))
|
||||
CreateSessionResult createSessionResult = await _listener.CreateSessionAsync(HostContext.RunnerShutdownToken);
|
||||
if (createSessionResult == CreateSessionResult.SessionConflict)
|
||||
{
|
||||
return Constants.Runner.ReturnCode.SessionConflict;
|
||||
}
|
||||
else if (createSessionResult == CreateSessionResult.Failure)
|
||||
{
|
||||
return Constants.Runner.ReturnCode.TerminatedError;
|
||||
}
|
||||
@@ -558,13 +580,21 @@ namespace GitHub.Runner.Listener
|
||||
await runServer.ConnectAsync(new Uri(messageRef.RunServiceUrl), creds);
|
||||
try
|
||||
{
|
||||
jobRequestMessage =
|
||||
await runServer.GetJobMessageAsync(messageRef.RunnerRequestId,
|
||||
messageQueueLoopTokenSource.Token);
|
||||
jobRequestMessage = await runServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
||||
_acquireJobThrottler.Reset();
|
||||
}
|
||||
catch (TaskOrchestrationJobAlreadyAcquiredException)
|
||||
catch (Exception ex) when (
|
||||
ex is TaskOrchestrationJobNotFoundException || // HTTP status 404
|
||||
ex is TaskOrchestrationJobAlreadyAcquiredException || // HTTP status 409
|
||||
ex is TaskOrchestrationJobUnprocessableException) // HTTP status 422
|
||||
{
|
||||
Trace.Info("Job is already acquired, skip this message.");
|
||||
Trace.Info($"Skipping message Job. {ex.Message}");
|
||||
await _acquireJobThrottler.IncrementAndWaitAsync(messageQueueLoopTokenSource.Token);
|
||||
continue;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error($"Caught exception from acquiring job message: {ex}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -598,6 +628,7 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
else if (string.Equals(message.MessageType, TaskAgentMessageTypes.ForceTokenRefresh))
|
||||
{
|
||||
Trace.Info("Received ForceTokenRefreshMessage");
|
||||
await _listener.RefreshListenerTokenAsync(messageQueueLoopTokenSource.Token);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64;win-arm64</RuntimeIdentifiers>
|
||||
<SelfContained>true</SelfContained>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<NoWarn>NU1701;NU1603</NoWarn>
|
||||
<NoWarn>NU1701;NU1603;SYSLIB0050;SYSLIB0051</NoWarn>
|
||||
<Version>$(Version)</Version>
|
||||
<PredefinedCulturesOnly>false</PredefinedCulturesOnly>
|
||||
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<OutputType>Library</OutputType>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64;win-arm64</RuntimeIdentifiers>
|
||||
<SelfContained>true</SelfContained>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<NoWarn>NU1701;NU1603</NoWarn>
|
||||
<NoWarn>NU1701;NU1603;SYSLIB0050;SYSLIB0051</NoWarn>
|
||||
<Version>$(Version)</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<OutputType>Library</OutputType>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64;win-arm64</RuntimeIdentifiers>
|
||||
<SelfContained>true</SelfContained>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<NoWarn>NU1701;NU1603</NoWarn>
|
||||
<NoWarn>NU1701;NU1603;SYSLIB0050;SYSLIB0051</NoWarn>
|
||||
<Version>$(Version)</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -14,9 +15,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.4.0" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="4.4.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
|
||||
@@ -459,6 +459,34 @@ namespace GitHub.Runner.Sdk
|
||||
File.WriteAllText(path, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces invalid file name characters with '_'
|
||||
/// </summary>
|
||||
public static string ReplaceInvalidFileNameChars(string fileName)
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
var invalidChars = Path.GetInvalidFileNameChars();
|
||||
|
||||
var current = 0; // Current index
|
||||
while (current < fileName?.Length)
|
||||
{
|
||||
var next = fileName.IndexOfAny(invalidChars, current);
|
||||
if (next >= 0)
|
||||
{
|
||||
result.Append(fileName.Substring(current, next - current));
|
||||
result.Append('_');
|
||||
current = next + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Append(fileName.Substring(current));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively enumerates a directory without following directory reparse points.
|
||||
/// </summary>
|
||||
|
||||
@@ -85,11 +85,6 @@ namespace GitHub.Runner.Sdk
|
||||
settings.SendTimeout = TimeSpan.FromSeconds(Math.Min(Math.Max(httpRequestTimeoutSeconds, 100), 1200));
|
||||
}
|
||||
|
||||
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("USE_BROKER_FLOW")))
|
||||
{
|
||||
settings.AllowAutoRedirectForBroker = true;
|
||||
}
|
||||
|
||||
// Remove Invariant from the list of accepted languages.
|
||||
//
|
||||
// The constructor of VssHttpRequestSettings (base class of VssClientHttpRequestSettings) adds the current
|
||||
|
||||
@@ -7,129 +7,6 @@ namespace GitHub.Runner.Sdk
|
||||
public static class WhichUtil
|
||||
{
|
||||
public static string Which(string command, bool require = false, ITraceWriter trace = null, string prependPath = null)
|
||||
{
|
||||
ArgUtil.NotNullOrEmpty(command, nameof(command));
|
||||
trace?.Info($"Which: '{command}'");
|
||||
if (Path.IsPathFullyQualified(command) && File.Exists(command))
|
||||
{
|
||||
trace?.Info($"Fully qualified path: '{command}'");
|
||||
return command;
|
||||
}
|
||||
string path = Environment.GetEnvironmentVariable(PathUtil.PathVariable);
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
trace?.Info("PATH environment variable not defined.");
|
||||
path = path ?? string.Empty;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(prependPath))
|
||||
{
|
||||
path = PathUtil.PrependPath(prependPath, path);
|
||||
}
|
||||
|
||||
string[] pathSegments = path.Split(new Char[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries);
|
||||
for (int i = 0; i < pathSegments.Length; i++)
|
||||
{
|
||||
pathSegments[i] = Environment.ExpandEnvironmentVariables(pathSegments[i]);
|
||||
}
|
||||
|
||||
foreach (string pathSegment in pathSegments)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(pathSegment) && Directory.Exists(pathSegment))
|
||||
{
|
||||
string[] matches = null;
|
||||
#if OS_WINDOWS
|
||||
string pathExt = Environment.GetEnvironmentVariable("PATHEXT");
|
||||
if (string.IsNullOrEmpty(pathExt))
|
||||
{
|
||||
// XP's system default value for PATHEXT system variable
|
||||
pathExt = ".com;.exe;.bat;.cmd;.vbs;.vbe;.js;.jse;.wsf;.wsh";
|
||||
}
|
||||
|
||||
string[] pathExtSegments = pathExt.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// if command already has an extension.
|
||||
if (pathExtSegments.Any(ext => command.EndsWith(ext, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
try
|
||||
{
|
||||
matches = Directory.GetFiles(pathSegment, command);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
trace?.Info("Ignore UnauthorizedAccess exception during Which.");
|
||||
trace?.Verbose(ex.ToString());
|
||||
}
|
||||
|
||||
if (matches != null && matches.Length > 0 && IsPathValid(matches.First(), trace))
|
||||
{
|
||||
trace?.Info($"Location: '{matches.First()}'");
|
||||
return matches.First();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string searchPattern;
|
||||
searchPattern = StringUtil.Format($"{command}.*");
|
||||
try
|
||||
{
|
||||
matches = Directory.GetFiles(pathSegment, searchPattern);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
trace?.Info("Ignore UnauthorizedAccess exception during Which.");
|
||||
trace?.Verbose(ex.ToString());
|
||||
}
|
||||
|
||||
if (matches != null && matches.Length > 0)
|
||||
{
|
||||
// add extension.
|
||||
for (int i = 0; i < pathExtSegments.Length; i++)
|
||||
{
|
||||
string fullPath = Path.Combine(pathSegment, $"{command}{pathExtSegments[i]}");
|
||||
if (matches.Any(p => p.Equals(fullPath, StringComparison.OrdinalIgnoreCase)) && IsPathValid(fullPath, trace))
|
||||
{
|
||||
trace?.Info($"Location: '{fullPath}'");
|
||||
return fullPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
try
|
||||
{
|
||||
matches = Directory.GetFiles(pathSegment, command);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
trace?.Info("Ignore UnauthorizedAccess exception during Which.");
|
||||
trace?.Verbose(ex.ToString());
|
||||
}
|
||||
|
||||
if (matches != null && matches.Length > 0 && IsPathValid(matches.First(), trace))
|
||||
{
|
||||
trace?.Info($"Location: '{matches.First()}'");
|
||||
return matches.First();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if OS_WINDOWS
|
||||
trace?.Info($"{command}: command not found. Make sure '{command}' is installed and its location included in the 'Path' environment variable.");
|
||||
#else
|
||||
trace?.Info($"{command}: command not found. Make sure '{command}' is installed and its location included in the 'PATH' environment variable.");
|
||||
#endif
|
||||
if (require)
|
||||
{
|
||||
throw new FileNotFoundException(
|
||||
message: $"{command}: command not found",
|
||||
fileName: command);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string Which2(string command, bool require = false, ITraceWriter trace = null, string prependPath = null)
|
||||
{
|
||||
ArgUtil.NotNullOrEmpty(command, nameof(command));
|
||||
trace?.Info($"Which2: '{command}'");
|
||||
|
||||
@@ -483,10 +483,6 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
// Load stored Ids for later load actions
|
||||
compositeAction.Steps[i].Id = _cachedEmbeddedStepIds[action.Id][i];
|
||||
if (string.IsNullOrEmpty(executionContext.Global.Variables.Get("DistributedTask.EnableCompositeActions")) && compositeAction.Steps[i].Reference.Type != Pipelines.ActionSourceType.Script)
|
||||
{
|
||||
throw new Exception("`uses:` keyword is not currently supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -703,11 +699,12 @@ namespace GitHub.Runner.Worker
|
||||
catch (Exception ex) when (!executionContext.CancellationToken.IsCancellationRequested) // Do not retry if the run is cancelled.
|
||||
{
|
||||
// UnresolvableActionDownloadInfoException is a 422 client error, don't retry
|
||||
// NonRetryableActionDownloadInfoException is an non-retryable exception from Actions
|
||||
// Some possible cases are:
|
||||
// * Repo is rate limited
|
||||
// * Repo or tag doesn't exist, or isn't public
|
||||
// * Policy validation failed
|
||||
if (attempt < 3 && !(ex is WebApi.UnresolvableActionDownloadInfoException))
|
||||
if (attempt < 3 && !(ex is WebApi.UnresolvableActionDownloadInfoException) && !(ex is WebApi.NonRetryableActionDownloadInfoException))
|
||||
{
|
||||
executionContext.Output($"Failed to resolve action download info. Error: {ex.Message}");
|
||||
executionContext.Debug(ex.ToString());
|
||||
@@ -796,43 +793,40 @@ namespace GitHub.Runner.Worker
|
||||
try
|
||||
{
|
||||
var useActionArchiveCache = false;
|
||||
if (executionContext.Global.Variables.GetBoolean("DistributedTask.UseActionArchiveCache") == true)
|
||||
var hasActionArchiveCache = false;
|
||||
var actionArchiveCacheDir = Environment.GetEnvironmentVariable(Constants.Variables.Agent.ActionArchiveCacheDirectory);
|
||||
if (!string.IsNullOrEmpty(actionArchiveCacheDir) &&
|
||||
Directory.Exists(actionArchiveCacheDir))
|
||||
{
|
||||
var hasActionArchiveCache = false;
|
||||
var actionArchiveCacheDir = Environment.GetEnvironmentVariable(Constants.Variables.Agent.ActionArchiveCacheDirectory);
|
||||
if (!string.IsNullOrEmpty(actionArchiveCacheDir) &&
|
||||
Directory.Exists(actionArchiveCacheDir))
|
||||
{
|
||||
hasActionArchiveCache = true;
|
||||
Trace.Info($"Check if action archive '{downloadInfo.ResolvedNameWithOwner}@{downloadInfo.ResolvedSha}' already exists in cache directory '{actionArchiveCacheDir}'");
|
||||
hasActionArchiveCache = true;
|
||||
Trace.Info($"Check if action archive '{downloadInfo.ResolvedNameWithOwner}@{downloadInfo.ResolvedSha}' already exists in cache directory '{actionArchiveCacheDir}'");
|
||||
#if OS_WINDOWS
|
||||
var cacheArchiveFile = Path.Combine(actionArchiveCacheDir, downloadInfo.ResolvedNameWithOwner.Replace(Path.DirectorySeparatorChar, '_').Replace(Path.AltDirectorySeparatorChar, '_'), $"{downloadInfo.ResolvedSha}.zip");
|
||||
var cacheArchiveFile = Path.Combine(actionArchiveCacheDir, downloadInfo.ResolvedNameWithOwner.Replace(Path.DirectorySeparatorChar, '_').Replace(Path.AltDirectorySeparatorChar, '_'), $"{downloadInfo.ResolvedSha}.zip");
|
||||
#else
|
||||
var cacheArchiveFile = Path.Combine(actionArchiveCacheDir, downloadInfo.ResolvedNameWithOwner.Replace(Path.DirectorySeparatorChar, '_').Replace(Path.AltDirectorySeparatorChar, '_'), $"{downloadInfo.ResolvedSha}.tar.gz");
|
||||
var cacheArchiveFile = Path.Combine(actionArchiveCacheDir, downloadInfo.ResolvedNameWithOwner.Replace(Path.DirectorySeparatorChar, '_').Replace(Path.AltDirectorySeparatorChar, '_'), $"{downloadInfo.ResolvedSha}.tar.gz");
|
||||
#endif
|
||||
if (File.Exists(cacheArchiveFile))
|
||||
if (File.Exists(cacheArchiveFile))
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
Trace.Info($"Found action archive '{cacheArchiveFile}' in cache directory '{actionArchiveCacheDir}'");
|
||||
File.Copy(cacheArchiveFile, archiveFile);
|
||||
useActionArchiveCache = true;
|
||||
executionContext.Debug($"Copied action archive '{cacheArchiveFile}' to '{archiveFile}'");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error($"Failed to copy action archive '{cacheArchiveFile}' to '{archiveFile}'. Error: {ex}");
|
||||
}
|
||||
Trace.Info($"Found action archive '{cacheArchiveFile}' in cache directory '{actionArchiveCacheDir}'");
|
||||
File.Copy(cacheArchiveFile, archiveFile);
|
||||
useActionArchiveCache = true;
|
||||
executionContext.Debug($"Copied action archive '{cacheArchiveFile}' to '{archiveFile}'");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error($"Failed to copy action archive '{cacheArchiveFile}' to '{archiveFile}'. Error: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
executionContext.Global.JobTelemetry.Add(new JobTelemetry()
|
||||
{
|
||||
Type = JobTelemetryType.General,
|
||||
Message = $"Action archive cache usage: {downloadInfo.ResolvedNameWithOwner}@{downloadInfo.ResolvedSha} use cache {useActionArchiveCache} has cache {hasActionArchiveCache}"
|
||||
});
|
||||
}
|
||||
|
||||
executionContext.Global.JobTelemetry.Add(new JobTelemetry()
|
||||
{
|
||||
Type = JobTelemetryType.General,
|
||||
Message = $"Action archive cache usage: {downloadInfo.ResolvedNameWithOwner}@{downloadInfo.ResolvedSha} use cache {useActionArchiveCache} has cache {hasActionArchiveCache}"
|
||||
});
|
||||
|
||||
if (!useActionArchiveCache)
|
||||
{
|
||||
await DownloadRepositoryArchive(executionContext, link, downloadInfo.Authentication?.Token, archiveFile);
|
||||
@@ -878,16 +872,9 @@ namespace GitHub.Runner.Worker
|
||||
int exitCode = await processInvoker.ExecuteAsync(stagingDirectory, tar, $"-xzf \"{archiveFile}\"", null, executionContext.CancellationToken);
|
||||
if (exitCode != 0)
|
||||
{
|
||||
if (executionContext.Global.Variables.GetBoolean("DistributedTask.DetailUntarFailure") == true)
|
||||
{
|
||||
var fileInfo = new FileInfo(archiveFile);
|
||||
var sha256hash = await IOUtil.GetFileContentSha256HashAsync(archiveFile);
|
||||
throw new InvalidActionArchiveException($"Can't use 'tar -xzf' extract archive file: {archiveFile} (SHA256 '{sha256hash}', size '{fileInfo.Length}' bytes, tar outputs '{string.Join(' ', tarOutputs)}'). Action being checked out: {downloadInfo.NameWithOwner}@{downloadInfo.Ref}. return code: {exitCode}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidActionArchiveException($"Can't use 'tar -xzf' extract archive file: {archiveFile}. Action being checked out: {downloadInfo.NameWithOwner}@{downloadInfo.Ref}. return code: {exitCode}.");
|
||||
}
|
||||
var fileInfo = new FileInfo(archiveFile);
|
||||
var sha256hash = await IOUtil.GetFileContentSha256HashAsync(archiveFile);
|
||||
throw new InvalidActionArchiveException($"Can't use 'tar -xzf' extract archive file: {archiveFile} (SHA256 '{sha256hash}', size '{fileInfo.Length}' bytes, tar outputs '{string.Join(' ', tarOutputs)}'). Action being checked out: {downloadInfo.NameWithOwner}@{downloadInfo.Ref}. return code: {exitCode}.");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1031,13 +1018,6 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var step in compositeAction.Steps)
|
||||
{
|
||||
if (string.IsNullOrEmpty(executionContext.Global.Variables.Get("DistributedTask.EnableCompositeActions")) && step.Reference.Type != Pipelines.ActionSourceType.Script)
|
||||
{
|
||||
throw new Exception("`uses:` keyword is not currently supported.");
|
||||
}
|
||||
}
|
||||
return setupInfo;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -466,17 +466,39 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to create directory to store registry client credentials: {e.Message}");
|
||||
}
|
||||
var loginExitCode = await _dockerManager.DockerLogin(
|
||||
executionContext,
|
||||
configLocation,
|
||||
container.RegistryServer,
|
||||
container.RegistryAuthUsername,
|
||||
container.RegistryAuthPassword);
|
||||
|
||||
if (loginExitCode != 0)
|
||||
// Login docker with retry up to 3 times
|
||||
int retryCount = 0;
|
||||
int loginExitCode = 0;
|
||||
while (retryCount < 3)
|
||||
{
|
||||
loginExitCode = await _dockerManager.DockerLogin(
|
||||
executionContext,
|
||||
configLocation,
|
||||
container.RegistryServer,
|
||||
container.RegistryAuthUsername,
|
||||
container.RegistryAuthPassword);
|
||||
if (loginExitCode == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
retryCount++;
|
||||
if (retryCount < 3)
|
||||
{
|
||||
var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(10));
|
||||
executionContext.Warning($"Docker login for '{container.RegistryServer}' failed with exit code {loginExitCode}, back off {backOff.TotalSeconds} seconds before retry.");
|
||||
await Task.Delay(backOff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (retryCount == 3 && loginExitCode != 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Docker login for '{container.RegistryServer}' failed with exit code {loginExitCode}");
|
||||
}
|
||||
|
||||
return configLocation;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,13 +91,13 @@ namespace GitHub.Runner.Worker
|
||||
string phaseName = executionContext.Global.Variables.System_PhaseDisplayName ?? "UnknownPhaseName";
|
||||
|
||||
// zip the files
|
||||
string diagnosticsZipFileName = $"{buildName}-{phaseName}.zip";
|
||||
string diagnosticsZipFileName = $"{buildName}-{IOUtil.ReplaceInvalidFileNameChars(phaseName)}.zip";
|
||||
string diagnosticsZipFilePath = Path.Combine(supportRootFolder, diagnosticsZipFileName);
|
||||
ZipFile.CreateFromDirectory(supportFilesFolder, diagnosticsZipFilePath);
|
||||
|
||||
// upload the json metadata file
|
||||
executionContext.Debug("Uploading diagnostic metadata file.");
|
||||
string metadataFileName = $"diagnostics-{buildName}-{phaseName}.json";
|
||||
string metadataFileName = $"diagnostics-{buildName}-{IOUtil.ReplaceInvalidFileNameChars(phaseName)}.json";
|
||||
string metadataFilePath = Path.Combine(supportFilesFolder, metadataFileName);
|
||||
string phaseResult = GetTaskResultAsString(executionContext.Result);
|
||||
|
||||
|
||||
@@ -244,7 +244,7 @@ namespace GitHub.Runner.Worker
|
||||
if (resultsReceiverEndpoint != null)
|
||||
{
|
||||
Trace.Info($"Queueing results file ({filePath}) for attachment upload ({attachmentName})");
|
||||
var stepId = context.Id;
|
||||
var stepId = context.IsEmbedded ? context.EmbeddedId : context.Id;
|
||||
// Attachments must be added to the parent context (job), not the current context (step)
|
||||
context.Root.QueueSummaryFile(attachmentName, scrubbedFilePath, stepId);
|
||||
}
|
||||
|
||||
@@ -223,6 +223,10 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
{
|
||||
Environment["ACTIONS_CACHE_URL"] = cacheUrl;
|
||||
}
|
||||
if (systemConnection.Data.TryGetValue("PipelinesServiceUrl", out var pipelinesServiceUrl) && !string.IsNullOrEmpty(pipelinesServiceUrl))
|
||||
{
|
||||
Environment["ACTIONS_RUNTIME_URL"] = pipelinesServiceUrl;
|
||||
}
|
||||
if (systemConnection.Data.TryGetValue("GenerateIdTokenUrl", out var generateIdTokenUrl) && !string.IsNullOrEmpty(generateIdTokenUrl))
|
||||
{
|
||||
Environment["ACTIONS_ID_TOKEN_REQUEST_URL"] = generateIdTokenUrl;
|
||||
|
||||
@@ -84,6 +84,45 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
}
|
||||
nodeData.NodeVersion = "node16";
|
||||
}
|
||||
|
||||
var localForceActionsToNode20 = StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable(Constants.Variables.Agent.ManualForceActionsToNode20));
|
||||
executionContext.Global.EnvironmentVariables.TryGetValue(Constants.Variables.Actions.ManualForceActionsToNode20, out var workflowForceActionsToNode20);
|
||||
var enforceNode20Locally = !string.IsNullOrWhiteSpace(workflowForceActionsToNode20) ? StringUtil.ConvertToBoolean(workflowForceActionsToNode20) : localForceActionsToNode20;
|
||||
if (string.Equals(nodeData.NodeVersion, "node16")
|
||||
&& ((executionContext.Global.Variables.GetBoolean("DistributedTask.ForceGithubJavascriptActionsToNode20") ?? false) || enforceNode20Locally))
|
||||
{
|
||||
executionContext.Global.EnvironmentVariables.TryGetValue(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion, out var workflowOptOut);
|
||||
var isWorkflowOptOutSet = !string.IsNullOrWhiteSpace(workflowOptOut);
|
||||
var isLocalOptOut = StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion));
|
||||
bool isOptOut = isWorkflowOptOutSet ? StringUtil.ConvertToBoolean(workflowOptOut) : isLocalOptOut;
|
||||
|
||||
if (!isOptOut)
|
||||
{
|
||||
var repoAction = action as Pipelines.RepositoryPathReference;
|
||||
if (repoAction != null)
|
||||
{
|
||||
var warningActions = new HashSet<string>();
|
||||
if (executionContext.Global.Variables.TryGetValue(Constants.Runner.EnforcedNode16DetectedAfterEndOfLifeEnvVariable, out var node20ForceWarnings))
|
||||
{
|
||||
warningActions = StringUtil.ConvertFromJson<HashSet<string>>(node20ForceWarnings);
|
||||
}
|
||||
|
||||
string 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(Constants.Runner.EnforcedNode16DetectedAfterEndOfLifeEnvVariable, StringUtil.ConvertToJson(warningActions));
|
||||
}
|
||||
nodeData.NodeVersion = "node20";
|
||||
}
|
||||
}
|
||||
(handler as INodeScriptActionHandler).Data = nodeData;
|
||||
}
|
||||
else if (data.ExecutionType == ActionExecutionType.Script)
|
||||
|
||||
@@ -58,6 +58,10 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
{
|
||||
Environment["ACTIONS_CACHE_URL"] = cacheUrl;
|
||||
}
|
||||
if (systemConnection.Data.TryGetValue("PipelinesServiceUrl", out var pipelinesServiceUrl) && !string.IsNullOrEmpty(pipelinesServiceUrl))
|
||||
{
|
||||
Environment["ACTIONS_RUNTIME_URL"] = pipelinesServiceUrl;
|
||||
}
|
||||
if (systemConnection.Data.TryGetValue("GenerateIdTokenUrl", out var generateIdTokenUrl) && !string.IsNullOrEmpty(generateIdTokenUrl))
|
||||
{
|
||||
Environment["ACTIONS_ID_TOKEN_REQUEST_URL"] = generateIdTokenUrl;
|
||||
@@ -114,6 +118,11 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
{
|
||||
Data.NodeVersion = "node16";
|
||||
}
|
||||
|
||||
if (forcedNodeVersion == "node20" && Data.NodeVersion != "node20")
|
||||
{
|
||||
Data.NodeVersion = "node20";
|
||||
}
|
||||
var nodeRuntimeVersion = await StepHost.DetermineNodeRuntimeVersion(ExecutionContext, Data.NodeVersion);
|
||||
string file = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), nodeRuntimeVersion, "bin", $"node{IOUtil.ExeExtension}");
|
||||
|
||||
|
||||
@@ -83,40 +83,19 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
shellCommand = "pwsh";
|
||||
if (validateShellOnHost)
|
||||
{
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
shellCommandPath = WhichUtil.Which2(shellCommand, require: false, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
shellCommandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath);
|
||||
}
|
||||
shellCommandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath);
|
||||
if (string.IsNullOrEmpty(shellCommandPath))
|
||||
{
|
||||
shellCommand = "powershell";
|
||||
Trace.Info($"Defaulting to {shellCommand}");
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
shellCommandPath = WhichUtil.Which2(shellCommand, require: true, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
shellCommandPath = WhichUtil.Which(shellCommand, require: true, Trace, prependPath);
|
||||
}
|
||||
Trace.Info($"Defaulting to {shellCommand}");
|
||||
shellCommandPath = WhichUtil.Which(shellCommand, require: true, Trace, prependPath);
|
||||
}
|
||||
}
|
||||
#else
|
||||
shellCommand = "sh";
|
||||
if (validateShellOnHost)
|
||||
{
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
shellCommandPath = WhichUtil.Which2("bash", false, Trace, prependPath) ?? WhichUtil.Which2("sh", true, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
shellCommandPath = WhichUtil.Which("bash", false, Trace, prependPath) ?? WhichUtil.Which("sh", true, Trace, prependPath);
|
||||
}
|
||||
shellCommandPath = WhichUtil.Which("bash", false, Trace, prependPath) ?? WhichUtil.Which("sh", true, Trace, prependPath);
|
||||
}
|
||||
#endif
|
||||
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shellCommand);
|
||||
@@ -127,14 +106,7 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
shellCommand = parsed.shellCommand;
|
||||
if (validateShellOnHost)
|
||||
{
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
shellCommandPath = WhichUtil.Which2(parsed.shellCommand, true, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
shellCommandPath = WhichUtil.Which(parsed.shellCommand, true, Trace, prependPath);
|
||||
}
|
||||
shellCommandPath = WhichUtil.Which(parsed.shellCommand, true, Trace, prependPath);
|
||||
}
|
||||
|
||||
argFormat = $"{parsed.shellArgs}".TrimStart();
|
||||
@@ -216,38 +188,17 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
{
|
||||
#if OS_WINDOWS
|
||||
shellCommand = "pwsh";
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
commandPath = WhichUtil.Which2(shellCommand, require: false, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
commandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath);
|
||||
}
|
||||
commandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath);
|
||||
if (string.IsNullOrEmpty(commandPath))
|
||||
{
|
||||
shellCommand = "powershell";
|
||||
Trace.Info($"Defaulting to {shellCommand}");
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
commandPath = WhichUtil.Which2(shellCommand, require: true, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
commandPath = WhichUtil.Which(shellCommand, require: true, Trace, prependPath);
|
||||
}
|
||||
commandPath = WhichUtil.Which(shellCommand, require: true, Trace, prependPath);
|
||||
}
|
||||
ArgUtil.NotNullOrEmpty(commandPath, "Default Shell");
|
||||
#else
|
||||
shellCommand = "sh";
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
commandPath = WhichUtil.Which2("bash", false, Trace, prependPath) ?? WhichUtil.Which2("sh", true, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
commandPath = WhichUtil.Which("bash", false, Trace, prependPath) ?? WhichUtil.Which("sh", true, Trace, prependPath);
|
||||
}
|
||||
commandPath = WhichUtil.Which("bash", false, Trace, prependPath) ?? WhichUtil.Which("sh", true, Trace, prependPath);
|
||||
#endif
|
||||
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shellCommand);
|
||||
}
|
||||
@@ -258,14 +209,7 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
if (!IsActionStep && systemShells.Contains(shell))
|
||||
{
|
||||
shellCommand = shell;
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
commandPath = WhichUtil.Which2(shell, !isContainerStepHost, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
commandPath = WhichUtil.Which(shell, !isContainerStepHost, Trace, prependPath);
|
||||
}
|
||||
commandPath = WhichUtil.Which(shell, !isContainerStepHost, Trace, prependPath);
|
||||
if (shell == "bash")
|
||||
{
|
||||
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat("sh");
|
||||
@@ -280,14 +224,7 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
var parsed = ScriptHandlerHelpers.ParseShellOptionString(shell);
|
||||
shellCommand = parsed.shellCommand;
|
||||
// For non-ContainerStepHost, the command must be located on the host by Which
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("DistributedTask.UseWhich2") == true)
|
||||
{
|
||||
commandPath = WhichUtil.Which2(parsed.shellCommand, !isContainerStepHost, Trace, prependPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
commandPath = WhichUtil.Which(parsed.shellCommand, !isContainerStepHost, Trace, prependPath);
|
||||
}
|
||||
commandPath = WhichUtil.Which(parsed.shellCommand, !isContainerStepHost, Trace, prependPath);
|
||||
argFormat = $"{parsed.shellArgs}".TrimStart();
|
||||
if (string.IsNullOrEmpty(argFormat))
|
||||
{
|
||||
|
||||
@@ -392,6 +392,18 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
|
||||
// Register custom image creation post-job step if the "snapshot" token is present in the message.
|
||||
var snapshotRequest = templateEvaluator.EvaluateJobSnapshotRequest(message.Snapshot, jobContext.ExpressionValues, jobContext.ExpressionFunctions);
|
||||
if (snapshotRequest != null)
|
||||
{
|
||||
var snapshotOperationProvider = HostContext.GetService<ISnapshotOperationProvider>();
|
||||
jobContext.RegisterPostJobStep(new JobExtensionRunner(
|
||||
runAsync: (executionContext, _) => snapshotOperationProvider.CreateSnapshotRequestAsync(executionContext, snapshotRequest),
|
||||
condition: $"{PipelineTemplateConstants.Success}()",
|
||||
displayName: $"Create custom image",
|
||||
data: null));
|
||||
}
|
||||
|
||||
// Register Job Completed hook if the variable is set
|
||||
var completedHookPath = Environment.GetEnvironmentVariable("ACTIONS_RUNNER_HOOK_JOB_COMPLETED");
|
||||
if (!string.IsNullOrEmpty(completedHookPath))
|
||||
|
||||
@@ -42,6 +42,7 @@ namespace GitHub.Runner.Worker
|
||||
Trace.Info("Job ID {0}", message.JobId);
|
||||
|
||||
DateTime jobStartTimeUtc = DateTime.UtcNow;
|
||||
_runnerSettings = HostContext.GetService<IConfigurationStore>().GetSettings();
|
||||
IRunnerService server = null;
|
||||
|
||||
// add orchestration id to useragent for better correlation.
|
||||
@@ -54,13 +55,6 @@ namespace GitHub.Runner.Worker
|
||||
VssUtil.InitializeVssClientSettings(HostContext.UserAgents, HostContext.WebProxy);
|
||||
}
|
||||
|
||||
var jobServerQueueTelemetry = false;
|
||||
if (message.Variables.TryGetValue("DistributedTask.EnableJobServerQueueTelemetry", out VariableValue enableJobServerQueueTelemetry) &&
|
||||
!string.IsNullOrEmpty(enableJobServerQueueTelemetry?.Value))
|
||||
{
|
||||
jobServerQueueTelemetry = StringUtil.ConvertToBoolean(enableJobServerQueueTelemetry.Value);
|
||||
}
|
||||
|
||||
ServiceEndpoint systemConnection = message.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
||||
if (MessageUtil.IsRunServiceJob(message.MessageType))
|
||||
{
|
||||
@@ -82,7 +76,7 @@ namespace GitHub.Runner.Worker
|
||||
launchServer.InitializeLaunchClient(new Uri(launchReceiverEndpoint), accessToken);
|
||||
}
|
||||
_jobServerQueue = HostContext.GetService<IJobServerQueue>();
|
||||
_jobServerQueue.Start(message, resultsServiceOnly: true, enableTelemetry: jobServerQueueTelemetry);
|
||||
_jobServerQueue.Start(message, resultsServiceOnly: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -104,7 +98,7 @@ namespace GitHub.Runner.Worker
|
||||
VssConnection jobConnection = VssUtil.CreateConnection(jobServerUrl, jobServerCredential, delegatingHandlers);
|
||||
await jobServer.ConnectAsync(jobConnection);
|
||||
|
||||
_jobServerQueue.Start(message, enableTelemetry: jobServerQueueTelemetry);
|
||||
_jobServerQueue.Start(message);
|
||||
server = jobServer;
|
||||
}
|
||||
|
||||
@@ -164,8 +158,6 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
jobContext.SetRunnerContext("os", VarUtil.OS);
|
||||
jobContext.SetRunnerContext("arch", VarUtil.OSArchitecture);
|
||||
|
||||
_runnerSettings = HostContext.GetService<IConfigurationStore>().GetSettings();
|
||||
jobContext.SetRunnerContext("name", _runnerSettings.AgentName);
|
||||
|
||||
if (jobContext.Global.Variables.TryGetValue(WellKnownDistributedTaskVariables.RunnerEnvironment, out var runnerEnvironment))
|
||||
@@ -298,6 +290,12 @@ namespace GitHub.Runner.Worker
|
||||
jobContext.Warning(string.Format(Constants.Runner.EnforcedNode12DetectedAfterEndOfLife, actions));
|
||||
}
|
||||
|
||||
if (jobContext.Global.Variables.TryGetValue(Constants.Runner.EnforcedNode16DetectedAfterEndOfLifeEnvVariable, out var node20ForceWarnings) && (jobContext.Global.Variables.GetBoolean("DistributedTask.ForceGithubJavascriptActionsToNode20") ?? false))
|
||||
{
|
||||
var actions = string.Join(", ", StringUtil.ConvertFromJson<HashSet<string>>(node20ForceWarnings));
|
||||
jobContext.Warning(string.Format(Constants.Runner.EnforcedNode16DetectedAfterEndOfLife, actions));
|
||||
}
|
||||
|
||||
await ShutdownQueue(throwOnFailure: false);
|
||||
|
||||
// Make sure to clean temp after file upload since they may be pending fileupload still use the TEMP dir.
|
||||
@@ -405,6 +403,12 @@ namespace GitHub.Runner.Worker
|
||||
jobContext.Warning(string.Format(Constants.Runner.EnforcedNode12DetectedAfterEndOfLife, actions));
|
||||
}
|
||||
|
||||
if (jobContext.Global.Variables.TryGetValue(Constants.Runner.EnforcedNode16DetectedAfterEndOfLifeEnvVariable, out var node20ForceWarnings))
|
||||
{
|
||||
var actions = string.Join(", ", StringUtil.ConvertFromJson<HashSet<string>>(node20ForceWarnings));
|
||||
jobContext.Warning(string.Format(Constants.Runner.EnforcedNode16DetectedAfterEndOfLife, actions));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var jobQueueTelemetry = await ShutdownQueue(throwOnFailure: true);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64;win-arm64</RuntimeIdentifiers>
|
||||
<SelfContained>true</SelfContained>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<NoWarn>NU1701;NU1603</NoWarn>
|
||||
<NoWarn>NU1701;NU1603;SYSLIB0050;SYSLIB0051</NoWarn>
|
||||
<Version>$(Version)</Version>
|
||||
<PredefinedCulturesOnly>false</PredefinedCulturesOnly>
|
||||
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
|
||||
@@ -18,9 +19,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="4.4.0" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="4.4.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="8.0.0" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
|
||||
<PackageReference Include="YamlDotNet.Signed" Version="5.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
32
src/Runner.Worker/SnapshotOperationProvider.cs
Normal file
32
src/Runner.Worker/SnapshotOperationProvider.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
#nullable enable
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.Pipelines;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Sdk;
|
||||
namespace GitHub.Runner.Worker;
|
||||
|
||||
[ServiceLocator(Default = typeof(SnapshotOperationProvider))]
|
||||
public interface ISnapshotOperationProvider : IRunnerService
|
||||
{
|
||||
Task CreateSnapshotRequestAsync(IExecutionContext executionContext, Snapshot snapshotRequest);
|
||||
}
|
||||
|
||||
public class SnapshotOperationProvider : RunnerService, ISnapshotOperationProvider
|
||||
{
|
||||
public Task CreateSnapshotRequestAsync(IExecutionContext executionContext, Snapshot snapshotRequest)
|
||||
{
|
||||
var snapshotRequestFilePath = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), ".snapshot", "request.json");
|
||||
var snapshotRequestDirectoryPath = Path.GetDirectoryName(snapshotRequestFilePath);
|
||||
if (snapshotRequestDirectoryPath != null)
|
||||
{
|
||||
Directory.CreateDirectory(snapshotRequestDirectoryPath);
|
||||
}
|
||||
|
||||
IOUtil.SaveObject(snapshotRequest, snapshotRequestFilePath);
|
||||
executionContext.Output($"Request written to: {snapshotRequestFilePath}");
|
||||
executionContext.Output("This request will be processed after the job completes. You will not receive any feedback on the snapshot process within the workflow logs of this job.");
|
||||
executionContext.Output("If the snapshot process is successful, you should see a new image with the requested name in the list of available custom images when creating a new GitHub-hosted Runner.");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -295,7 +295,7 @@ namespace GitHub.Runner.Worker
|
||||
!jobCancellationToken.IsCancellationRequested)
|
||||
{
|
||||
Trace.Error($"Caught timeout exception from step: {ex.Message}");
|
||||
step.ExecutionContext.Error("The action has timed out.");
|
||||
step.ExecutionContext.Error($"The action '{step.DisplayName}' has timed out after {timeoutMinutes} minutes.");
|
||||
step.ExecutionContext.Result = TaskResult.Failed;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace GitHub.Services.Common
|
||||
|
||||
public String PropertyName { get; set; }
|
||||
|
||||
[Obsolete]
|
||||
[SecurityCritical]
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
|
||||
@@ -127,6 +127,7 @@ namespace GitHub.Services.Common
|
||||
EventId = (int)info.GetValue("m_eventId", typeof(int));
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
[SecurityCritical]
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
|
||||
@@ -214,25 +214,7 @@ namespace GitHub.Services.Common
|
||||
// ConfigureAwait(false) enables the continuation to be run outside any captured
|
||||
// SyncronizationContext (such as ASP.NET's) which keeps things from deadlocking...
|
||||
|
||||
var tmpResponse = await m_messageInvoker.SendAsync(request, tokenSource.Token).ConfigureAwait(false);
|
||||
if (Settings.AllowAutoRedirectForBroker && tmpResponse.StatusCode == HttpStatusCode.Redirect)
|
||||
{
|
||||
//Dispose of the previous response
|
||||
tmpResponse?.Dispose();
|
||||
|
||||
var location = tmpResponse.Headers.Location;
|
||||
request = new HttpRequestMessage(HttpMethod.Get, location);
|
||||
|
||||
// Reapply the token to new redirected request
|
||||
ApplyToken(request, token, applyICredentialsToWebProxy: lastResponseDemandedProxyAuth);
|
||||
|
||||
// Resend the request
|
||||
response = await m_messageInvoker.SendAsync(request, tokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
response = tmpResponse;
|
||||
}
|
||||
response = await m_messageInvoker.SendAsync(request, tokenSource.Token).ConfigureAwait(false);
|
||||
|
||||
traceInfo?.TraceRequestSendTime();
|
||||
|
||||
|
||||
@@ -110,16 +110,6 @@ namespace GitHub.Services.Common
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not HttpClientHandler should follow redirect on outgoing broker requests
|
||||
/// This is special since this also sends token in the request, where as default AllowAutoRedirect does not
|
||||
/// </summary>
|
||||
public Boolean AllowAutoRedirectForBroker
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not compression should be used on outgoing requests.
|
||||
/// The default value is true.
|
||||
|
||||
@@ -43,6 +43,7 @@ namespace GitHub.DistributedTask.Pipelines
|
||||
TemplateToken jobOutputs,
|
||||
IList<TemplateToken> defaults,
|
||||
ActionsEnvironmentReference actionsEnvironment,
|
||||
TemplateToken snapshot,
|
||||
String messageType = JobRequestMessageTypes.PipelineAgentJobRequest)
|
||||
{
|
||||
this.MessageType = messageType;
|
||||
@@ -57,6 +58,7 @@ namespace GitHub.DistributedTask.Pipelines
|
||||
this.Workspace = workspaceOptions;
|
||||
this.JobOutputs = jobOutputs;
|
||||
this.ActionsEnvironment = actionsEnvironment;
|
||||
this.Snapshot = snapshot;
|
||||
m_variables = new Dictionary<String, VariableValue>(variables, StringComparer.OrdinalIgnoreCase);
|
||||
m_maskHints = new List<MaskHint>(maskHints);
|
||||
m_steps = new List<JobStep>(steps);
|
||||
@@ -237,6 +239,13 @@ namespace GitHub.DistributedTask.Pipelines
|
||||
set;
|
||||
}
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public TemplateToken Snapshot
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of variables associated with the current context.
|
||||
/// </summary>
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
||||
public const String Id = "id";
|
||||
public const String If = "if";
|
||||
public const String Image = "image";
|
||||
public const String ImageName = "image-name";
|
||||
public const String Include = "include";
|
||||
public const String Inputs = "inputs";
|
||||
public const String Job = "job";
|
||||
@@ -60,6 +61,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
||||
public const String Services = "services";
|
||||
public const String Shell = "shell";
|
||||
public const String Skipped = "skipped";
|
||||
public const String Snapshot = "snapshot";
|
||||
public const String StepEnv = "step-env";
|
||||
public const String StepIfResult = "step-if-result";
|
||||
public const String StepWith = "step-with";
|
||||
|
||||
@@ -346,6 +346,39 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static Snapshot ConvertToJobSnapshotRequest(TemplateContext context, TemplateToken token)
|
||||
{
|
||||
string imageName = null;
|
||||
if (token is StringToken snapshotStringLiteral)
|
||||
{
|
||||
imageName = snapshotStringLiteral.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
var snapshotMapping = token.AssertMapping($"{PipelineTemplateConstants.Snapshot}");
|
||||
foreach (var snapshotPropertyPair in snapshotMapping)
|
||||
{
|
||||
var propertyName = snapshotPropertyPair.Key.AssertString($"{PipelineTemplateConstants.Snapshot} key");
|
||||
switch (propertyName.Value)
|
||||
{
|
||||
case PipelineTemplateConstants.ImageName:
|
||||
imageName = snapshotPropertyPair.Value.AssertString($"{PipelineTemplateConstants.Snapshot} {propertyName}").Value;
|
||||
break;
|
||||
default:
|
||||
propertyName.AssertUnexpectedValue($"{PipelineTemplateConstants.Snapshot} key");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(imageName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Snapshot(imageName);
|
||||
}
|
||||
|
||||
private static ActionStep ConvertToStep(
|
||||
TemplateContext context,
|
||||
TemplateToken stepsItem,
|
||||
|
||||
@@ -370,6 +370,32 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
||||
return result;
|
||||
}
|
||||
|
||||
public Snapshot EvaluateJobSnapshotRequest(TemplateToken token,
|
||||
DictionaryContextData contextData,
|
||||
IList<IFunctionInfo> expressionFunctions)
|
||||
{
|
||||
var result = default(Snapshot);
|
||||
|
||||
if (token != null && token.Type != TokenType.Null)
|
||||
{
|
||||
var context = CreateContext(contextData, expressionFunctions);
|
||||
try
|
||||
{
|
||||
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.Snapshot, token, 0, null, omitHeader: true);
|
||||
context.Errors.Check();
|
||||
result = PipelineTemplateConverter.ConvertToJobSnapshotRequest(context, token);
|
||||
}
|
||||
catch (Exception ex) when (!(ex is TemplateValidationException))
|
||||
{
|
||||
context.Errors.Add(ex);
|
||||
}
|
||||
|
||||
context.Errors.Check();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private TemplateContext CreateContext(
|
||||
DictionaryContextData contextData,
|
||||
IList<IFunctionInfo> expressionFunctions,
|
||||
|
||||
17
src/Sdk/DTPipelines/Pipelines/Snapshot.cs
Normal file
17
src/Sdk/DTPipelines/Pipelines/Snapshot.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace GitHub.DistributedTask.Pipelines
|
||||
{
|
||||
[DataContract]
|
||||
public class Snapshot
|
||||
{
|
||||
public Snapshot(string imageName)
|
||||
{
|
||||
ImageName = imageName;
|
||||
}
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String ImageName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,8 @@
|
||||
"env": "job-env",
|
||||
"outputs": "job-outputs",
|
||||
"defaults": "job-defaults",
|
||||
"steps": "steps"
|
||||
"steps": "steps",
|
||||
"snapshot": "snapshot"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -155,6 +156,24 @@
|
||||
}
|
||||
},
|
||||
|
||||
"snapshot": {
|
||||
"one-of": [
|
||||
"non-empty-string",
|
||||
"snapshot-mapping"
|
||||
]
|
||||
},
|
||||
|
||||
"snapshot-mapping": {
|
||||
"mapping": {
|
||||
"properties": {
|
||||
"image-name": {
|
||||
"type": "non-empty-string",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"runs-on": {
|
||||
"context": [
|
||||
"github",
|
||||
|
||||
95
src/Sdk/DTWebApi/WebApi/ActionsRunServerHttpClient.cs
Normal file
95
src/Sdk/DTWebApi/WebApi/ActionsRunServerHttpClient.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.Common.Diagnostics;
|
||||
using GitHub.Services.WebApi;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace GitHub.DistributedTask.WebApi
|
||||
{
|
||||
[ResourceArea(TaskResourceIds.AreaId)]
|
||||
public class ActionsRunServerHttpClient : TaskAgentHttpClient
|
||||
{
|
||||
private static readonly JsonSerializerSettings s_serializerSettings;
|
||||
|
||||
static ActionsRunServerHttpClient()
|
||||
{
|
||||
s_serializerSettings = new VssJsonMediaTypeFormatter().SerializerSettings;
|
||||
s_serializerSettings.DateParseHandling = DateParseHandling.None;
|
||||
s_serializerSettings.FloatParseHandling = FloatParseHandling.Double;
|
||||
}
|
||||
|
||||
public ActionsRunServerHttpClient(
|
||||
Uri baseUrl,
|
||||
VssCredentials credentials)
|
||||
: base(baseUrl, credentials)
|
||||
{
|
||||
}
|
||||
|
||||
public ActionsRunServerHttpClient(
|
||||
Uri baseUrl,
|
||||
VssCredentials credentials,
|
||||
VssHttpRequestSettings settings)
|
||||
: base(baseUrl, credentials, settings)
|
||||
{
|
||||
}
|
||||
|
||||
public ActionsRunServerHttpClient(
|
||||
Uri baseUrl,
|
||||
VssCredentials credentials,
|
||||
params DelegatingHandler[] handlers)
|
||||
: base(baseUrl, credentials, handlers)
|
||||
{
|
||||
}
|
||||
|
||||
public ActionsRunServerHttpClient(
|
||||
Uri baseUrl,
|
||||
VssCredentials credentials,
|
||||
VssHttpRequestSettings settings,
|
||||
params DelegatingHandler[] handlers)
|
||||
: base(baseUrl, credentials, settings, handlers)
|
||||
{
|
||||
}
|
||||
|
||||
public ActionsRunServerHttpClient(
|
||||
Uri baseUrl,
|
||||
HttpMessageHandler pipeline,
|
||||
Boolean disposeHandler)
|
||||
: base(baseUrl, pipeline, disposeHandler)
|
||||
{
|
||||
}
|
||||
|
||||
public Task<Pipelines.AgentJobRequestMessage> GetJobMessageAsync(
|
||||
string messageId,
|
||||
object userState = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
HttpMethod httpMethod = new HttpMethod("GET");
|
||||
Guid locationId = new Guid("25adab70-1379-4186-be8e-b643061ebe3a");
|
||||
object routeValues = new { messageId = messageId };
|
||||
|
||||
return SendAsync<Pipelines.AgentJobRequestMessage>(
|
||||
httpMethod,
|
||||
locationId,
|
||||
routeValues: routeValues,
|
||||
version: new ApiResourceVersion(6.0, 1),
|
||||
userState: userState,
|
||||
cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
protected override async Task<T> ReadJsonContentAsync<T>(HttpResponseMessage response, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
return JsonConvert.DeserializeObject<T>(json, s_serializerSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1539,6 +1539,26 @@ namespace GitHub.DistributedTask.WebApi
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
[ExceptionMapping("0.0", "3.0", "TaskOrchestrationJobUnprocessableException", "GitHub.DistributedTask.WebApi.TaskOrchestrationJobUnprocessableException, GitHub.DistributedTask.WebApi, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
|
||||
public sealed class TaskOrchestrationJobUnprocessableException : DistributedTaskException
|
||||
{
|
||||
public TaskOrchestrationJobUnprocessableException(String message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public TaskOrchestrationJobUnprocessableException(String message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
private TaskOrchestrationJobUnprocessableException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
[ExceptionMapping("0.0", "3.0", "TaskOrchestrationPlanSecurityException", "GitHub.DistributedTask.WebApi.TaskOrchestrationPlanSecurityException, GitHub.DistributedTask.WebApi, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
|
||||
public sealed class TaskOrchestrationPlanSecurityException : DistributedTaskException
|
||||
@@ -2498,6 +2518,25 @@ namespace GitHub.DistributedTask.WebApi
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class NonRetryableActionDownloadInfoException : DistributedTaskException
|
||||
{
|
||||
public NonRetryableActionDownloadInfoException(String message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public NonRetryableActionDownloadInfoException(String message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected NonRetryableActionDownloadInfoException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class FailedToResolveActionDownloadInfoException : DistributedTaskException
|
||||
{
|
||||
|
||||
@@ -141,24 +141,6 @@ namespace GitHub.DistributedTask.WebApi
|
||||
return ReplaceAgentAsync(poolId, agent.Id, agent, userState, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<Pipelines.AgentJobRequestMessage> GetJobMessageAsync(
|
||||
string messageId,
|
||||
object userState = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
HttpMethod httpMethod = new HttpMethod("GET");
|
||||
Guid locationId = new Guid("25adab70-1379-4186-be8e-b643061ebe3a");
|
||||
object routeValues = new { messageId = messageId };
|
||||
|
||||
return SendAsync<Pipelines.AgentJobRequestMessage>(
|
||||
httpMethod,
|
||||
locationId,
|
||||
routeValues: routeValues,
|
||||
version: new ApiResourceVersion(6.0, 1),
|
||||
userState: userState,
|
||||
cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
protected Task<T> SendAsync<T>(
|
||||
HttpMethod method,
|
||||
Guid locationId,
|
||||
|
||||
@@ -7,5 +7,8 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
{
|
||||
[DataMember(Name = "jobMessageId", EmitDefaultValue = false)]
|
||||
public string JobMessageId { get; set; }
|
||||
|
||||
[DataMember(Name = "runnerOS", EmitDefaultValue = false)]
|
||||
public string RunnerOS { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
17
src/Sdk/RSWebApi/Contracts/RunServiceError.cs
Normal file
17
src/Sdk/RSWebApi/Contracts/RunServiceError.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace GitHub.Actions.RunService.WebApi
|
||||
{
|
||||
[DataContract]
|
||||
public class RunServiceError
|
||||
{
|
||||
[DataMember(Name = "source", EmitDefaultValue = false)]
|
||||
public string Source { get; set; }
|
||||
|
||||
[DataMember(Name = "statusCode", EmitDefaultValue = false)]
|
||||
public int Code { get; set; }
|
||||
|
||||
[DataMember(Name = "errorMessage", EmitDefaultValue = false)]
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.OAuth;
|
||||
using GitHub.Services.WebApi;
|
||||
using Newtonsoft.Json;
|
||||
using Sdk.RSWebApi.Contracts;
|
||||
using Sdk.WebApi.WebApi;
|
||||
|
||||
@@ -16,6 +17,15 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
{
|
||||
public class RunServiceHttpClient : RawHttpClientBase
|
||||
{
|
||||
private static readonly JsonSerializerSettings s_serializerSettings;
|
||||
|
||||
static RunServiceHttpClient()
|
||||
{
|
||||
s_serializerSettings = new VssJsonMediaTypeFormatter().SerializerSettings;
|
||||
s_serializerSettings.DateParseHandling = DateParseHandling.None;
|
||||
s_serializerSettings.FloatParseHandling = FloatParseHandling.Double;
|
||||
}
|
||||
|
||||
public RunServiceHttpClient(
|
||||
Uri baseUrl,
|
||||
VssOAuthCredential credentials)
|
||||
@@ -59,12 +69,14 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
public async Task<AgentJobRequestMessage> GetJobMessageAsync(
|
||||
Uri requestUri,
|
||||
string messageId,
|
||||
string runnerOS,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
HttpMethod httpMethod = new HttpMethod("POST");
|
||||
var payload = new AcquireJobRequest
|
||||
{
|
||||
JobMessageId = messageId,
|
||||
RunnerOS = runnerOS
|
||||
};
|
||||
|
||||
requestUri = new Uri(requestUri, "acquirejob");
|
||||
@@ -74,6 +86,7 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
httpMethod,
|
||||
requestUri: requestUri,
|
||||
content: requestContent,
|
||||
readErrorBody: true,
|
||||
cancellationToken: cancellationToken);
|
||||
|
||||
if (result.IsSuccess)
|
||||
@@ -81,14 +94,35 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
return result.Value;
|
||||
}
|
||||
|
||||
if (TryParseErrorBody(result.ErrorBody, out RunServiceError error))
|
||||
{
|
||||
switch ((HttpStatusCode)error.Code)
|
||||
{
|
||||
case HttpStatusCode.NotFound:
|
||||
throw new TaskOrchestrationJobNotFoundException($"Job message not found '{messageId}'. {error.Message}");
|
||||
case HttpStatusCode.Conflict:
|
||||
throw new TaskOrchestrationJobAlreadyAcquiredException($"Job message already acquired '{messageId}'. {error.Message}");
|
||||
case HttpStatusCode.UnprocessableEntity:
|
||||
throw new TaskOrchestrationJobUnprocessableException($"Unprocessable job '{messageId}'. {error.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// Temporary back compat
|
||||
switch (result.StatusCode)
|
||||
{
|
||||
case HttpStatusCode.NotFound:
|
||||
throw new TaskOrchestrationJobNotFoundException($"Job message not found: {messageId}");
|
||||
case HttpStatusCode.Conflict:
|
||||
throw new TaskOrchestrationJobAlreadyAcquiredException($"Job message already acquired: {messageId}");
|
||||
default:
|
||||
throw new Exception($"Failed to get job message: {result.Error}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(result.ErrorBody))
|
||||
{
|
||||
throw new Exception($"Failed to get job message: {result.Error}. {Truncate(result.ErrorBody)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Failed to get job message: {result.Error}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +130,7 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
Uri requestUri,
|
||||
Guid planId,
|
||||
Guid jobId,
|
||||
TaskResult result,
|
||||
TaskResult conclusion,
|
||||
Dictionary<String, VariableValue> outputs,
|
||||
IList<StepResult> stepResults,
|
||||
IList<Annotation> jobAnnotations,
|
||||
@@ -108,7 +142,7 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
{
|
||||
PlanID = planId,
|
||||
JobID = jobId,
|
||||
Conclusion = result,
|
||||
Conclusion = conclusion,
|
||||
Outputs = outputs,
|
||||
StepResults = stepResults,
|
||||
Annotations = jobAnnotations,
|
||||
@@ -118,22 +152,39 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
requestUri = new Uri(requestUri, "completejob");
|
||||
|
||||
var requestContent = new ObjectContent<CompleteJobRequest>(payload, new VssJsonMediaTypeFormatter(true));
|
||||
var response = await SendAsync(
|
||||
var result = await Send2Async(
|
||||
httpMethod,
|
||||
requestUri,
|
||||
content: requestContent,
|
||||
cancellationToken: cancellationToken);
|
||||
if (response.IsSuccessStatusCode)
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (response.StatusCode)
|
||||
if (TryParseErrorBody(result.ErrorBody, out RunServiceError error))
|
||||
{
|
||||
switch ((HttpStatusCode)error.Code)
|
||||
{
|
||||
case HttpStatusCode.NotFound:
|
||||
throw new TaskOrchestrationJobNotFoundException($"Job not found: {jobId}. {error.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// Temporary back compat
|
||||
switch (result.StatusCode)
|
||||
{
|
||||
case HttpStatusCode.NotFound:
|
||||
throw new TaskOrchestrationJobNotFoundException($"Job not found: {jobId}");
|
||||
default:
|
||||
throw new Exception($"Failed to complete job: {response.ReasonPhrase}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(result.ErrorBody))
|
||||
{
|
||||
throw new Exception($"Failed to complete job: {result.Error}. {Truncate(result.ErrorBody)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Failed to complete job: {result.Error}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +208,7 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
httpMethod,
|
||||
requestUri,
|
||||
content: requestContent,
|
||||
readErrorBody: true,
|
||||
cancellationToken: cancellationToken);
|
||||
|
||||
if (result.IsSuccess)
|
||||
@@ -164,13 +216,67 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
return result.Value;
|
||||
}
|
||||
|
||||
if (TryParseErrorBody(result.ErrorBody, out RunServiceError error))
|
||||
{
|
||||
switch ((HttpStatusCode)error.Code)
|
||||
{
|
||||
case HttpStatusCode.NotFound:
|
||||
throw new TaskOrchestrationJobNotFoundException($"Job not found: {jobId}. {error.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// Temporary back compat
|
||||
switch (result.StatusCode)
|
||||
{
|
||||
case HttpStatusCode.NotFound:
|
||||
throw new TaskOrchestrationJobNotFoundException($"Job not found: {jobId}");
|
||||
default:
|
||||
throw new Exception($"Failed to renew job: {result.Error}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(result.ErrorBody))
|
||||
{
|
||||
throw new Exception($"Failed to renew job: {result.Error}. {Truncate(result.ErrorBody)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Failed to renew job: {result.Error}");
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task<T> ReadJsonContentAsync<T>(HttpResponseMessage response, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
return JsonConvert.DeserializeObject<T>(json, s_serializerSettings);
|
||||
}
|
||||
|
||||
private static bool TryParseErrorBody(string errorBody, out RunServiceError error)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(errorBody))
|
||||
{
|
||||
try
|
||||
{
|
||||
error = JsonUtility.FromString<RunServiceError>(errorBody);
|
||||
if (error?.Source == "actions-run-service")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
error = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string Truncate(string errorBody)
|
||||
{
|
||||
if (errorBody.Length > 100)
|
||||
{
|
||||
return errorBody.Substring(0, 100) + "[truncated]";
|
||||
}
|
||||
|
||||
return errorBody;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<OutputType>Library</OutputType>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64;win-arm64</RuntimeIdentifiers>
|
||||
<!-- <SelfContained>true</SelfContained> -->
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<NoWarn>NU1701;NU1603</NoWarn>
|
||||
<NoWarn>NU1701;NU1603;SYSLIB0050;SYSLIB0051</NoWarn>
|
||||
<Version>$(Version)</Version>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
@@ -14,13 +15,13 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.19.1" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.9" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.2.1" />
|
||||
<PackageReference Include="System.Security.Cryptography.Cng" Version="4.4.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="4.4.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.5.1" />
|
||||
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="8.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
||||
<PackageReference Include="Minimatch" Version="2.0.0" />
|
||||
<PackageReference Include="YamlDotNet.Signed" Version="5.3.0" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
|
||||
@@ -110,9 +110,14 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
return result.Value;
|
||||
}
|
||||
|
||||
// the only time we throw a `Forbidden` exception from Listener /messages is when the runner is
|
||||
// disable_update and is too old to poll
|
||||
if (result.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
throw new AccessDeniedException(result.Error);
|
||||
throw new AccessDeniedException($"{result.Error} Runner version v{runnerVersion} is deprecated and cannot receive messages.")
|
||||
{
|
||||
ErrorCode = 1
|
||||
};
|
||||
}
|
||||
|
||||
throw new Exception($"Failed to get job message: {result.Error}");
|
||||
|
||||
@@ -85,6 +85,7 @@ namespace GitHub.Services.OAuth
|
||||
set;
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
base.GetObjectData(info, context);
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace Sdk.WebApi.WebApi
|
||||
}
|
||||
}
|
||||
|
||||
protected Task<RawHttpClientResult<T>> SendAsync<T>(
|
||||
protected async Task<RawHttpClientResult> Send2Async(
|
||||
HttpMethod method,
|
||||
Uri requestUri,
|
||||
HttpContent content = null,
|
||||
@@ -109,7 +109,47 @@ namespace Sdk.WebApi.WebApi
|
||||
Object userState = null,
|
||||
CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return SendAsync<T>(method, null, requestUri, content, queryParameters, userState, cancellationToken);
|
||||
using (var response = await SendAsync(method, requestUri, content, queryParameters, userState, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return new RawHttpClientResult(
|
||||
isSuccess: true,
|
||||
error: string.Empty,
|
||||
statusCode: response.StatusCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorBody = default(string);
|
||||
try
|
||||
{
|
||||
errorBody = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorBody = $"Error reading HTTP response body: {ex.Message}";
|
||||
}
|
||||
|
||||
string errorMessage = $"Error: {response.ReasonPhrase}";
|
||||
return new RawHttpClientResult(
|
||||
isSuccess: false,
|
||||
error: errorMessage,
|
||||
statusCode: response.StatusCode,
|
||||
errorBody: errorBody);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Task<RawHttpClientResult<T>> SendAsync<T>(
|
||||
HttpMethod method,
|
||||
Uri requestUri,
|
||||
HttpContent content = null,
|
||||
IEnumerable<KeyValuePair<String, String>> queryParameters = null,
|
||||
Boolean readErrorBody = false,
|
||||
Object userState = null,
|
||||
CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return SendAsync<T>(method, null, requestUri, content, queryParameters, readErrorBody, userState, cancellationToken);
|
||||
}
|
||||
|
||||
protected async Task<RawHttpClientResult<T>> SendAsync<T>(
|
||||
@@ -118,18 +158,20 @@ namespace Sdk.WebApi.WebApi
|
||||
Uri requestUri,
|
||||
HttpContent content = null,
|
||||
IEnumerable<KeyValuePair<String, String>> queryParameters = null,
|
||||
Boolean readErrorBody = false,
|
||||
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);
|
||||
return await SendAsync<T>(requestMessage, readErrorBody, userState, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task<RawHttpClientResult<T>> SendAsync<T>(
|
||||
HttpRequestMessage message,
|
||||
Boolean readErrorBody = false,
|
||||
Object userState = null,
|
||||
CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
@@ -145,8 +187,21 @@ namespace Sdk.WebApi.WebApi
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorBody = default(string);
|
||||
if (readErrorBody)
|
||||
{
|
||||
try
|
||||
{
|
||||
errorBody = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorBody = $"Error reading HTTP response body: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
string errorMessage = $"Error: {response.ReasonPhrase}";
|
||||
return RawHttpClientResult<T>.Fail(errorMessage, response.StatusCode);
|
||||
return RawHttpClientResult<T>.Fail(errorMessage, response.StatusCode, errorBody);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,15 +5,27 @@ namespace Sdk.WebApi.WebApi
|
||||
public class RawHttpClientResult
|
||||
{
|
||||
public bool IsSuccess { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// A description of the HTTP status code, like "Error: Unprocessable Entity"
|
||||
/// </summary>
|
||||
public string Error { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The HTTP response body for unsuccessful HTTP status codes, or an error message when reading the response body fails.
|
||||
/// </summary>
|
||||
public string ErrorBody { get; protected set; }
|
||||
|
||||
public HttpStatusCode StatusCode { get; protected set; }
|
||||
|
||||
public bool IsFailure => !IsSuccess;
|
||||
|
||||
protected RawHttpClientResult(bool isSuccess, string error, HttpStatusCode statusCode)
|
||||
public RawHttpClientResult(bool isSuccess, string error, HttpStatusCode statusCode, string errorBody = null)
|
||||
{
|
||||
IsSuccess = isSuccess;
|
||||
Error = error;
|
||||
StatusCode = statusCode;
|
||||
ErrorBody = errorBody;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,13 +33,13 @@ namespace Sdk.WebApi.WebApi
|
||||
{
|
||||
public T Value { get; private set; }
|
||||
|
||||
protected internal RawHttpClientResult(T value, bool isSuccess, string error, HttpStatusCode statusCode)
|
||||
: base(isSuccess, error, statusCode)
|
||||
protected internal RawHttpClientResult(T value, bool isSuccess, string error, HttpStatusCode statusCode, string errorBody)
|
||||
: base(isSuccess, error, statusCode, errorBody)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static RawHttpClientResult<T> Fail(string message, HttpStatusCode statusCode) => new RawHttpClientResult<T>(default(T), false, message, statusCode);
|
||||
public static RawHttpClientResult<T> Ok(T value) => new RawHttpClientResult<T>(value, true, string.Empty, HttpStatusCode.OK);
|
||||
public static RawHttpClientResult<T> Fail(string message, HttpStatusCode statusCode, string errorBody) => new RawHttpClientResult<T>(default(T), false, message, statusCode, errorBody);
|
||||
public static RawHttpClientResult<T> Ok(T value) => new RawHttpClientResult<T>(value, true, string.Empty, HttpStatusCode.OK, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace GitHub.Services.WebApi
|
||||
HttpStatusCode = (HttpStatusCode)info.GetInt32("HttpStatusCode");
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
[SecurityCritical]
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace GitHub.Runner.Common.Tests
|
||||
trace.Info("Parsed");
|
||||
|
||||
trace.Info("Commands: {0}", clp.Commands.Count);
|
||||
Assert.True(clp.Commands.Count == 2);
|
||||
Assert.Equal(2, clp.Commands.Count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace GitHub.Runner.Common.Tests
|
||||
trace.Info("Parsed");
|
||||
|
||||
trace.Info("Args: {0}", clp.Args.Count);
|
||||
Assert.True(clp.Args.Count == 2);
|
||||
Assert.Equal(2, clp.Args.Count);
|
||||
Assert.True(clp.Args.ContainsKey("arg1"));
|
||||
Assert.Equal("arg1val", clp.Args["arg1"]);
|
||||
Assert.True(clp.Args.ContainsKey("arg2"));
|
||||
@@ -112,7 +112,7 @@ namespace GitHub.Runner.Common.Tests
|
||||
trace.Info("Parsed");
|
||||
|
||||
trace.Info("Args: {0}", clp.Flags.Count);
|
||||
Assert.True(clp.Flags.Count == 2);
|
||||
Assert.Equal(2, clp.Flags.Count);
|
||||
Assert.Contains("flag1", clp.Flags);
|
||||
Assert.Contains("flag2", clp.Flags);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace GitHub.Runner.Common.Tests
|
||||
"osx-arm64"
|
||||
};
|
||||
|
||||
Assert.True(BuildConstants.Source.CommitHash.Length == 40, $"CommitHash should be SHA-1 hash {BuildConstants.Source.CommitHash}");
|
||||
Assert.Equal(40, BuildConstants.Source.CommitHash.Length);
|
||||
Assert.True(validPackageNames.Contains(BuildConstants.RunnerPackage.PackageName), $"PackageName should be one of the following '{string.Join(", ", validPackageNames)}', current PackageName is '{BuildConstants.RunnerPackage.PackageName}'");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,11 +56,11 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
BrokerMessageListener listener = new();
|
||||
listener.Initialize(tc);
|
||||
|
||||
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
trace.Info("result: {0}", result);
|
||||
|
||||
// Assert.
|
||||
Assert.True(result);
|
||||
Assert.Equal(CreateSessionResult.Success, result);
|
||||
_brokerServer
|
||||
.Verify(x => x.CreateSessionAsync(
|
||||
It.Is<TaskAgentSession>(y => y != null),
|
||||
|
||||
@@ -806,7 +806,7 @@ namespace GitHub.Runner.Common.Tests
|
||||
"test runner" });
|
||||
|
||||
// Assert.
|
||||
Assert.True(command.Validate().Count == 0);
|
||||
Assert.Equal(0, command.Validate().Count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -844,7 +844,7 @@ namespace GitHub.Runner.Common.Tests
|
||||
var command = new CommandSettings(hc, args: new string[] { validCommand, $"--{flag}" });
|
||||
|
||||
// Assert.
|
||||
Assert.True(command.Validate().Count == 0);
|
||||
Assert.Equal(0, command.Validate().Count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -874,7 +874,7 @@ namespace GitHub.Runner.Common.Tests
|
||||
var command = new CommandSettings(hc, args: new string[] { validCommand, $"--{arg}", argValue });
|
||||
|
||||
// Assert.
|
||||
Assert.True(command.Validate().Count == 0);
|
||||
Assert.Equal(0, command.Validate().Count);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -190,11 +190,11 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
||||
trace.Info("Configured, verifying all the parameter value");
|
||||
var s = configManager.LoadSettings();
|
||||
Assert.NotNull(s);
|
||||
Assert.True(s.ServerUrl.Equals(_expectedServerUrl));
|
||||
Assert.True(s.AgentName.Equals(_expectedAgentName));
|
||||
Assert.True(s.PoolId.Equals(_secondRunnerGroupId));
|
||||
Assert.True(s.WorkFolder.Equals(_expectedWorkFolder));
|
||||
Assert.True(s.Ephemeral.Equals(true));
|
||||
Assert.Equal(_expectedServerUrl, s.ServerUrl);
|
||||
Assert.Equal(_expectedAgentName, s.AgentName);
|
||||
Assert.Equal(_secondRunnerGroupId, s.PoolId);
|
||||
Assert.Equal(_expectedWorkFolder, s.WorkFolder);
|
||||
Assert.True(s.Ephemeral);
|
||||
|
||||
// validate GetAgentPoolsAsync gets called twice with automation pool type
|
||||
_runnerServer.Verify(x => x.GetAgentPoolsAsync(It.IsAny<string>(), It.Is<TaskAgentPoolType>(p => p == TaskAgentPoolType.Automation)), Times.Exactly(2));
|
||||
@@ -292,11 +292,11 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
||||
trace.Info("Configured, verifying all the parameter value");
|
||||
var s = configManager.LoadSettings();
|
||||
Assert.NotNull(s);
|
||||
Assert.True(s.ServerUrl.Equals(_expectedServerUrl));
|
||||
Assert.True(s.AgentName.Equals(_expectedAgentName));
|
||||
Assert.True(s.PoolId.Equals(_secondRunnerGroupId));
|
||||
Assert.True(s.WorkFolder.Equals(_expectedWorkFolder));
|
||||
Assert.True(s.Ephemeral.Equals(true));
|
||||
Assert.Equal(_expectedServerUrl, s.ServerUrl);
|
||||
Assert.Equal(_expectedAgentName, s.AgentName);
|
||||
Assert.Equal(_secondRunnerGroupId, s.PoolId);
|
||||
Assert.Equal(_expectedWorkFolder, s.WorkFolder);
|
||||
Assert.True(s.Ephemeral);
|
||||
|
||||
// validate GetAgentPoolsAsync gets called twice with automation pool type
|
||||
_runnerServer.Verify(x => x.GetAgentPoolsAsync(It.IsAny<string>(), It.Is<TaskAgentPoolType>(p => p == TaskAgentPoolType.Automation)), Times.Exactly(2));
|
||||
|
||||
213
src/Test/L0/Listener/ErrorThrottlerL0.cs
Normal file
213
src/Test/L0/Listener/ErrorThrottlerL0.cs
Normal file
@@ -0,0 +1,213 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Listener;
|
||||
using GitHub.Runner.Listener.Configuration;
|
||||
using GitHub.Runner.Common.Tests;
|
||||
using System.Runtime.CompilerServices;
|
||||
using GitHub.Services.WebApi;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests.Listener
|
||||
{
|
||||
public sealed class ErrorThrottlerL0
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(1)]
|
||||
[InlineData(2)]
|
||||
[InlineData(3)]
|
||||
[InlineData(4)]
|
||||
[InlineData(5)]
|
||||
[InlineData(6)]
|
||||
[InlineData(7)]
|
||||
[InlineData(8)]
|
||||
public async void TestIncrementAndWait(int totalAttempts)
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
// Arrange
|
||||
var errorThrottler = new ErrorThrottler();
|
||||
errorThrottler.Initialize(hc);
|
||||
var eventArgs = new List<DelayEventArgs>();
|
||||
hc.Delaying += (sender, args) =>
|
||||
{
|
||||
eventArgs.Add(args);
|
||||
};
|
||||
|
||||
// Act
|
||||
for (int attempt = 1; attempt <= totalAttempts; attempt++)
|
||||
{
|
||||
await errorThrottler.IncrementAndWaitAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.Equal(totalAttempts - 1, eventArgs.Count);
|
||||
for (int i = 0; i < eventArgs.Count; i++)
|
||||
{
|
||||
// Expected milliseconds
|
||||
int expectedMin;
|
||||
int expectedMax;
|
||||
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
expectedMin = 1000; // Min backoff
|
||||
expectedMax = 1000;
|
||||
break;
|
||||
case 1:
|
||||
expectedMin = 1800; // Min + 0.8 * Coefficient
|
||||
expectedMax = 2200; // Min + 1.2 * Coefficient
|
||||
break;
|
||||
case 2:
|
||||
expectedMin = 3400; // Min + 0.8 * Coefficient * 3
|
||||
expectedMax = 4600; // Min + 1.2 * Coefficient * 3
|
||||
break;
|
||||
case 3:
|
||||
expectedMin = 6600; // Min + 0.8 * Coefficient * 7
|
||||
expectedMax = 9400; // Min + 1.2 * Coefficient * 7
|
||||
break;
|
||||
case 4:
|
||||
expectedMin = 13000; // Min + 0.8 * Coefficient * 15
|
||||
expectedMax = 19000; // Min + 1.2 * Coefficient * 15
|
||||
break;
|
||||
case 5:
|
||||
expectedMin = 25800; // Min + 0.8 * Coefficient * 31
|
||||
expectedMax = 38200; // Min + 1.2 * Coefficient * 31
|
||||
break;
|
||||
case 6:
|
||||
expectedMin = 51400; // Min + 0.8 * Coefficient * 63
|
||||
expectedMax = 60000; // Max backoff
|
||||
break;
|
||||
case 7:
|
||||
expectedMin = 60000;
|
||||
expectedMax = 60000;
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException("Unexpected eventArgs count");
|
||||
}
|
||||
|
||||
var actualMilliseconds = eventArgs[i].Delay.TotalMilliseconds;
|
||||
Assert.True(expectedMin <= actualMilliseconds, $"Unexpected min delay for eventArgs[{i}]. Expected min {expectedMin}, actual {actualMilliseconds}");
|
||||
Assert.True(expectedMax >= actualMilliseconds, $"Unexpected max delay for eventArgs[{i}]. Expected max {expectedMax}, actual {actualMilliseconds}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void TestReset()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
// Arrange
|
||||
var errorThrottler = new ErrorThrottler();
|
||||
errorThrottler.Initialize(hc);
|
||||
var eventArgs = new List<DelayEventArgs>();
|
||||
hc.Delaying += (sender, args) =>
|
||||
{
|
||||
eventArgs.Add(args);
|
||||
};
|
||||
|
||||
// Act
|
||||
await errorThrottler.IncrementAndWaitAsync(CancellationToken.None);
|
||||
await errorThrottler.IncrementAndWaitAsync(CancellationToken.None);
|
||||
await errorThrottler.IncrementAndWaitAsync(CancellationToken.None);
|
||||
errorThrottler.Reset();
|
||||
await errorThrottler.IncrementAndWaitAsync(CancellationToken.None);
|
||||
await errorThrottler.IncrementAndWaitAsync(CancellationToken.None);
|
||||
await errorThrottler.IncrementAndWaitAsync(CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(4, eventArgs.Count);
|
||||
for (int i = 0; i < eventArgs.Count; i++)
|
||||
{
|
||||
// Expected milliseconds
|
||||
int expectedMin;
|
||||
int expectedMax;
|
||||
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
case 2:
|
||||
expectedMin = 1000; // Min backoff
|
||||
expectedMax = 1000;
|
||||
break;
|
||||
case 1:
|
||||
case 3:
|
||||
expectedMin = 1800; // Min + 0.8 * Coefficient
|
||||
expectedMax = 2200; // Min + 1.2 * Coefficient
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException("Unexpected eventArgs count");
|
||||
}
|
||||
|
||||
var actualMilliseconds = eventArgs[i].Delay.TotalMilliseconds;
|
||||
Assert.True(expectedMin <= actualMilliseconds, $"Unexpected min delay for eventArgs[{i}]. Expected min {expectedMin}, actual {actualMilliseconds}");
|
||||
Assert.True(expectedMax >= actualMilliseconds, $"Unexpected max delay for eventArgs[{i}]. Expected max {expectedMax}, actual {actualMilliseconds}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void TestReceivesCancellationToken()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
// Arrange
|
||||
var errorThrottler = new ErrorThrottler();
|
||||
errorThrottler.Initialize(hc);
|
||||
var eventArgs = new List<DelayEventArgs>();
|
||||
hc.Delaying += (sender, args) =>
|
||||
{
|
||||
eventArgs.Add(args);
|
||||
};
|
||||
var cancellationTokenSource1 = new CancellationTokenSource();
|
||||
var cancellationTokenSource2 = new CancellationTokenSource();
|
||||
var cancellationTokenSource3 = new CancellationTokenSource();
|
||||
|
||||
// Act
|
||||
await errorThrottler.IncrementAndWaitAsync(cancellationTokenSource1.Token);
|
||||
await errorThrottler.IncrementAndWaitAsync(cancellationTokenSource2.Token);
|
||||
await errorThrottler.IncrementAndWaitAsync(cancellationTokenSource3.Token);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, eventArgs.Count);
|
||||
Assert.Equal(cancellationTokenSource2.Token, eventArgs[0].Token);
|
||||
Assert.Equal(cancellationTokenSource3.Token, eventArgs[1].Token);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void TestReceivesSender()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
// Arrange
|
||||
var errorThrottler = new ErrorThrottler();
|
||||
errorThrottler.Initialize(hc);
|
||||
var senders = new List<object>();
|
||||
hc.Delaying += (sender, args) =>
|
||||
{
|
||||
senders.Add(sender);
|
||||
};
|
||||
|
||||
// Act
|
||||
await errorThrottler.IncrementAndWaitAsync(CancellationToken.None);
|
||||
await errorThrottler.IncrementAndWaitAsync(CancellationToken.None);
|
||||
await errorThrottler.IncrementAndWaitAsync(CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, senders.Count);
|
||||
Assert.Equal(hc, senders[0]);
|
||||
Assert.Equal(hc, senders[1]);
|
||||
}
|
||||
}
|
||||
|
||||
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
|
||||
{
|
||||
return new TestHostContext(this, testName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
TaskOrchestrationPlanReference plan = new();
|
||||
TimelineReference timeline = null;
|
||||
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, null);
|
||||
result.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
|
||||
return result;
|
||||
}
|
||||
@@ -734,7 +734,10 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
await jobDispatcher.WaitAsync(CancellationToken.None);
|
||||
|
||||
Assert.True(jobDispatcher.RunOnceJobCompleted.Task.IsCompleted, "JobDispatcher should set task complete token for one time agent.");
|
||||
Assert.True(jobDispatcher.RunOnceJobCompleted.Task.Result, "JobDispatcher should set task complete token to 'TRUE' for one time agent.");
|
||||
if (jobDispatcher.RunOnceJobCompleted.Task.IsCompleted)
|
||||
{
|
||||
Assert.True(await jobDispatcher.RunOnceJobCompleted.Task, "JobDispatcher should set task complete token to 'TRUE' for one time agent.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -806,7 +809,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
},
|
||||
null,
|
||||
new List<TemplateToken>(),
|
||||
new ActionsEnvironmentReference("env")
|
||||
new ActionsEnvironmentReference("env"),
|
||||
null
|
||||
);
|
||||
return message;
|
||||
}
|
||||
|
||||
@@ -75,11 +75,11 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
MessageListener listener = new();
|
||||
listener.Initialize(tc);
|
||||
|
||||
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
trace.Info("result: {0}", result);
|
||||
|
||||
// Assert.
|
||||
Assert.True(result);
|
||||
Assert.Equal(CreateSessionResult.Success, result);
|
||||
_runnerServer
|
||||
.Verify(x => x.CreateAgentSessionAsync(
|
||||
_settings.PoolId,
|
||||
@@ -135,11 +135,11 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
MessageListener listener = new();
|
||||
listener.Initialize(tc);
|
||||
|
||||
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
trace.Info("result: {0}", result);
|
||||
|
||||
// Assert.
|
||||
Assert.True(result);
|
||||
Assert.Equal(CreateSessionResult.Success, result);
|
||||
|
||||
_runnerServer
|
||||
.Verify(x => x.CreateAgentSessionAsync(
|
||||
@@ -185,8 +185,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
MessageListener listener = new();
|
||||
listener.Initialize(tc);
|
||||
|
||||
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
Assert.True(result);
|
||||
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
Assert.Equal(CreateSessionResult.Success, result);
|
||||
|
||||
_runnerServer
|
||||
.Setup(x => x.DeleteAgentSessionAsync(
|
||||
@@ -245,10 +245,10 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
MessageListener listener = new();
|
||||
listener.Initialize(tc);
|
||||
|
||||
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
trace.Info("result: {0}", result);
|
||||
|
||||
Assert.True(result);
|
||||
Assert.Equal(CreateSessionResult.Success, result);
|
||||
|
||||
_runnerServer
|
||||
.Verify(x => x.CreateAgentSessionAsync(
|
||||
@@ -272,7 +272,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
//Assert
|
||||
_runnerServer
|
||||
.Verify(x => x.DeleteAgentSessionAsync(
|
||||
_settings.PoolId, expectedSession.SessionId, It.IsAny<CancellationToken>()), Times.Never());
|
||||
_settings.PoolId, expectedBrokerSession.SessionId, It.IsAny<CancellationToken>()), Times.Once());
|
||||
_brokerServer
|
||||
.Verify(x => x.DeleteSessionAsync(It.IsAny<CancellationToken>()), Times.Once());
|
||||
}
|
||||
@@ -309,8 +309,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
MessageListener listener = new();
|
||||
listener.Initialize(tc);
|
||||
|
||||
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
Assert.True(result);
|
||||
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
Assert.Equal(CreateSessionResult.Success, result);
|
||||
|
||||
var arMessages = new TaskAgentMessage[]
|
||||
{
|
||||
@@ -390,8 +390,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
MessageListener listener = new();
|
||||
listener.Initialize(tc);
|
||||
|
||||
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
Assert.True(result);
|
||||
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
Assert.Equal(CreateSessionResult.Success, result);
|
||||
|
||||
var brokerMigrationMesage = new BrokerMigrationMessage(new Uri("https://actions.broker.com"));
|
||||
|
||||
@@ -497,11 +497,11 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
MessageListener listener = new();
|
||||
listener.Initialize(tc);
|
||||
|
||||
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
trace.Info("result: {0}", result);
|
||||
|
||||
// Assert.
|
||||
Assert.True(result);
|
||||
Assert.Equal(CreateSessionResult.Success, result);
|
||||
_runnerServer
|
||||
.Verify(x => x.CreateAgentSessionAsync(
|
||||
_settings.PoolId,
|
||||
@@ -541,8 +541,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
MessageListener listener = new();
|
||||
listener.Initialize(tc);
|
||||
|
||||
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
Assert.True(result);
|
||||
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||
Assert.Equal(CreateSessionResult.Success, result);
|
||||
|
||||
_runnerServer
|
||||
.Setup(x => x.GetAgentMessageAsync(
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
private Mock<ITerminal> _term;
|
||||
private Mock<IConfigurationStore> _configStore;
|
||||
private Mock<ISelfUpdater> _updater;
|
||||
private Mock<IErrorThrottler> _acquireJobThrottler;
|
||||
|
||||
public RunnerL0()
|
||||
{
|
||||
@@ -35,6 +36,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
_term = new Mock<ITerminal>();
|
||||
_configStore = new Mock<IConfigurationStore>();
|
||||
_updater = new Mock<ISelfUpdater>();
|
||||
_acquireJobThrottler = new Mock<IErrorThrottler>();
|
||||
}
|
||||
|
||||
private Pipelines.AgentJobRequestMessage CreateJobRequestMessage(string jobName)
|
||||
@@ -42,7 +44,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
TaskOrchestrationPlanReference plan = new();
|
||||
TimelineReference timeline = null;
|
||||
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, null);
|
||||
}
|
||||
|
||||
private JobCancelMessage CreateJobCancelMessage()
|
||||
@@ -67,6 +69,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
||||
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
||||
runner.Initialize(hc);
|
||||
var settings = new RunnerSettings
|
||||
{
|
||||
@@ -88,7 +91,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
_configurationManager.Setup(x => x.IsConfigured())
|
||||
.Returns(true);
|
||||
_messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.FromResult<bool>(true));
|
||||
.Returns(Task.FromResult<CreateSessionResult>(CreateSessionResult.Success));
|
||||
_messageListener.Setup(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns(async () =>
|
||||
{
|
||||
@@ -126,7 +129,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
//wait for the runner to run one job
|
||||
if (!await signalWorkerComplete.WaitAsync(2000))
|
||||
{
|
||||
Assert.True(false, $"{nameof(_messageListener.Object.GetNextMessageAsync)} was not invoked.");
|
||||
Assert.Fail($"{nameof(_messageListener.Object.GetNextMessageAsync)} was not invoked.");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -174,6 +177,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
||||
hc.SetSingleton<IMessageListener>(_messageListener.Object);
|
||||
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
||||
|
||||
var command = new CommandSettings(hc, args);
|
||||
|
||||
@@ -184,7 +188,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
_configStore.Setup(x => x.IsServiceConfigured()).Returns(configureAsService);
|
||||
|
||||
_messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.FromResult(false));
|
||||
.Returns(Task.FromResult<CreateSessionResult>(CreateSessionResult.Failure));
|
||||
|
||||
var runner = new Runner.Listener.Runner();
|
||||
runner.Initialize(hc);
|
||||
@@ -205,6 +209,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
||||
hc.SetSingleton<IMessageListener>(_messageListener.Object);
|
||||
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
||||
|
||||
var command = new CommandSettings(hc, new[] { "run" });
|
||||
|
||||
@@ -217,7 +222,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
.Returns(false);
|
||||
|
||||
_messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.FromResult(false));
|
||||
.Returns(Task.FromResult<CreateSessionResult>(CreateSessionResult.Failure));
|
||||
|
||||
var runner = new Runner.Listener.Runner();
|
||||
runner.Initialize(hc);
|
||||
@@ -242,6 +247,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
||||
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
||||
runner.Initialize(hc);
|
||||
var settings = new RunnerSettings
|
||||
{
|
||||
@@ -263,7 +269,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
_configurationManager.Setup(x => x.IsConfigured())
|
||||
.Returns(true);
|
||||
_messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.FromResult<bool>(true));
|
||||
.Returns(Task.FromResult<CreateSessionResult>(CreateSessionResult.Success));
|
||||
_messageListener.Setup(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns(async () =>
|
||||
{
|
||||
@@ -305,8 +311,11 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
await Task.WhenAny(runnerTask, Task.Delay(30000));
|
||||
|
||||
Assert.True(runnerTask.IsCompleted, $"{nameof(runner.ExecuteCommand)} timed out.");
|
||||
Assert.True(!runnerTask.IsFaulted, runnerTask.Exception?.ToString());
|
||||
Assert.True(runnerTask.Result == Constants.Runner.ReturnCode.Success);
|
||||
Assert.False(runnerTask.IsFaulted, runnerTask.Exception?.ToString());
|
||||
if (runnerTask.IsCompleted)
|
||||
{
|
||||
Assert.Equal(Constants.Runner.ReturnCode.Success, await runnerTask);
|
||||
}
|
||||
|
||||
_jobDispatcher.Verify(x => x.Run(It.IsAny<Pipelines.AgentJobRequestMessage>(), true), Times.Once(),
|
||||
$"{nameof(_jobDispatcher.Object.Run)} was not invoked.");
|
||||
@@ -335,6 +344,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
||||
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
||||
runner.Initialize(hc);
|
||||
var settings = new RunnerSettings
|
||||
{
|
||||
@@ -363,7 +373,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
_configurationManager.Setup(x => x.IsConfigured())
|
||||
.Returns(true);
|
||||
_messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.FromResult<bool>(true));
|
||||
.Returns(Task.FromResult<CreateSessionResult>(CreateSessionResult.Success));
|
||||
_messageListener.Setup(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns(async () =>
|
||||
{
|
||||
@@ -406,7 +416,10 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
|
||||
Assert.True(runnerTask.IsCompleted, $"{nameof(runner.ExecuteCommand)} timed out.");
|
||||
Assert.True(!runnerTask.IsFaulted, runnerTask.Exception?.ToString());
|
||||
Assert.True(runnerTask.Result == Constants.Runner.ReturnCode.Success);
|
||||
if (runnerTask.IsCompleted)
|
||||
{
|
||||
Assert.Equal(Constants.Runner.ReturnCode.Success, await runnerTask);
|
||||
}
|
||||
|
||||
_jobDispatcher.Verify(x => x.Run(It.IsAny<Pipelines.AgentJobRequestMessage>(), true), Times.Once(),
|
||||
$"{nameof(_jobDispatcher.Object.Run)} was not invoked.");
|
||||
@@ -433,6 +446,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||
hc.SetSingleton<ISelfUpdater>(_updater.Object);
|
||||
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
||||
|
||||
runner.Initialize(hc);
|
||||
var settings = new RunnerSettings
|
||||
@@ -458,7 +472,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
_configurationManager.Setup(x => x.IsConfigured())
|
||||
.Returns(true);
|
||||
_messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.FromResult<bool>(true));
|
||||
.Returns(Task.FromResult<CreateSessionResult>(CreateSessionResult.Success));
|
||||
_messageListener.Setup(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns(async () =>
|
||||
{
|
||||
@@ -492,7 +506,10 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
|
||||
Assert.True(runnerTask.IsCompleted, $"{nameof(runner.ExecuteCommand)} timed out.");
|
||||
Assert.True(!runnerTask.IsFaulted, runnerTask.Exception?.ToString());
|
||||
Assert.True(runnerTask.Result == Constants.Runner.ReturnCode.RunOnceRunnerUpdating);
|
||||
if (runnerTask.IsCompleted)
|
||||
{
|
||||
Assert.Equal(Constants.Runner.ReturnCode.RunOnceRunnerUpdating, await runnerTask);
|
||||
}
|
||||
|
||||
_updater.Verify(x => x.SelfUpdate(It.IsAny<AgentRefreshMessage>(), It.IsAny<IJobDispatcher>(), false, It.IsAny<CancellationToken>()), Times.Once);
|
||||
_jobDispatcher.Verify(x => x.Run(It.IsAny<Pipelines.AgentJobRequestMessage>(), true), Times.Never());
|
||||
@@ -513,6 +530,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
hc.SetSingleton<IConfigurationManager>(_configurationManager.Object);
|
||||
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
||||
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
||||
|
||||
var command = new CommandSettings(hc, new[] { "remove", "--local" });
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace GitHub.Runner.Common.Tests
|
||||
trace.Error(ex);
|
||||
}
|
||||
|
||||
Assert.True(false, "Fail to retrive process environment variable.");
|
||||
Assert.Fail("Failed to retrieve process environment variable.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -65,7 +65,14 @@ namespace GitHub.Runner.Common.Tests
|
||||
}
|
||||
}
|
||||
|
||||
Assert.True(badCode.Count == 0, $"The following code is using Raw HttpClientHandler() which will not follow the proxy setting agent have. Please use HostContext.CreateHttpClientHandler() instead.\n {string.Join("\n", badCode)}");
|
||||
if (badCode.Count > 0)
|
||||
{
|
||||
Assert.Fail($"The following code is using Raw HttpClientHandler() which will not follow the proxy setting agent have. Please use HostContext.CreateHttpClientHandler() instead.\n {string.Join("\n", badCode)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.True(true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -112,7 +119,14 @@ namespace GitHub.Runner.Common.Tests
|
||||
}
|
||||
}
|
||||
|
||||
Assert.True(badCode.Count == 0, $"The following code is using Raw HttpClient() which will not follow the proxy setting agent have. Please use New HttpClient(HostContext.CreateHttpClientHandler()) instead.\n {string.Join("\n", badCode)}");
|
||||
if (badCode.Count > 0)
|
||||
{
|
||||
Assert.Fail($"The following code is using Raw HttpClient() which will not follow the proxy setting agent have. Please use New HttpClient(HostContext.CreateHttpClientHandler()) instead.\n {string.Join("\n", badCode)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.True(true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -30,9 +30,11 @@ namespace GitHub.Runner.Common.Tests
|
||||
private string _tempDirectoryRoot = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("D"));
|
||||
private StartupType _startupType;
|
||||
public event EventHandler Unloading;
|
||||
public event EventHandler<DelayEventArgs> Delaying;
|
||||
public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token;
|
||||
public ShutdownReason RunnerShutdownReason { get; private set; }
|
||||
public ISecretMasker SecretMasker => _secretMasker;
|
||||
|
||||
public TestHostContext(object testClass, [CallerMemberName] string testName = "")
|
||||
{
|
||||
ArgUtil.NotNull(testClass, nameof(testClass));
|
||||
@@ -92,6 +94,14 @@ namespace GitHub.Runner.Common.Tests
|
||||
|
||||
public async Task Delay(TimeSpan delay, CancellationToken token)
|
||||
{
|
||||
// Event callback
|
||||
EventHandler<DelayEventArgs> handler = Delaying;
|
||||
if (handler != null)
|
||||
{
|
||||
handler(this, new DelayEventArgs(delay, token));
|
||||
}
|
||||
|
||||
// Delay zero
|
||||
await Task.Delay(TimeSpan.Zero);
|
||||
}
|
||||
|
||||
@@ -361,4 +371,19 @@ namespace GitHub.Runner.Common.Tests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DelayEventArgs : EventArgs
|
||||
{
|
||||
public DelayEventArgs(
|
||||
TimeSpan delay,
|
||||
CancellationToken token)
|
||||
{
|
||||
Delay = delay;
|
||||
Token = token;
|
||||
}
|
||||
|
||||
public TimeSpan Delay { get; }
|
||||
|
||||
public CancellationToken Token { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -960,6 +960,33 @@ namespace GitHub.Runner.Common.Tests.Util
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void ReplaceInvalidFileNameChars()
|
||||
{
|
||||
Assert.Equal(string.Empty, IOUtil.ReplaceInvalidFileNameChars(null));
|
||||
Assert.Equal(string.Empty, IOUtil.ReplaceInvalidFileNameChars(string.Empty));
|
||||
Assert.Equal("hello.txt", IOUtil.ReplaceInvalidFileNameChars("hello.txt"));
|
||||
#if OS_WINDOWS
|
||||
// Refer https://github.com/dotnet/runtime/blob/ce84f1d8a3f12711bad678a33efbc37b461f684f/src/libraries/System.Private.CoreLib/src/System/IO/Path.Windows.cs#L15
|
||||
Assert.Equal(
|
||||
"1_ 2_ 3_ 4_ 5_ 6_ 7_ 8_ 9_ 10_ 11_ 12_ 13_ 14_ 15_ 16_ 17_ 18_ 19_ 20_ 21_ 22_ 23_ 24_ 25_ 26_ 27_ 28_ 29_ 30_ 31_ 32_ 33_ 34_ 35_ 36_ 37_ 38_ 39_ 40_ 41_",
|
||||
IOUtil.ReplaceInvalidFileNameChars($"1\" 2< 3> 4| 5\0 6{(char)1} 7{(char)2} 8{(char)3} 9{(char)4} 10{(char)5} 11{(char)6} 12{(char)7} 13{(char)8} 14{(char)9} 15{(char)10} 16{(char)11} 17{(char)12} 18{(char)13} 19{(char)14} 20{(char)15} 21{(char)16} 22{(char)17} 23{(char)18} 24{(char)19} 25{(char)20} 26{(char)21} 27{(char)22} 28{(char)23} 29{(char)24} 30{(char)25} 31{(char)26} 32{(char)27} 33{(char)28} 34{(char)29} 35{(char)30} 36{(char)31} 37: 38* 39? 40\\ 41/"));
|
||||
#else
|
||||
// Refer https://github.com/dotnet/runtime/blob/ce84f1d8a3f12711bad678a33efbc37b461f684f/src/libraries/System.Private.CoreLib/src/System/IO/Path.Unix.cs#L12
|
||||
Assert.Equal("1_ 2_", IOUtil.ReplaceInvalidFileNameChars("1\0 2/"));
|
||||
#endif
|
||||
Assert.Equal("_leading", IOUtil.ReplaceInvalidFileNameChars("/leading"));
|
||||
Assert.Equal("__consecutive leading", IOUtil.ReplaceInvalidFileNameChars("//consecutive leading"));
|
||||
Assert.Equal("trailing_", IOUtil.ReplaceInvalidFileNameChars("trailing/"));
|
||||
Assert.Equal("consecutive trailing__", IOUtil.ReplaceInvalidFileNameChars("consecutive trailing//"));
|
||||
Assert.Equal("middle_middle", IOUtil.ReplaceInvalidFileNameChars("middle/middle"));
|
||||
Assert.Equal("consecutive middle__consecutive middle", IOUtil.ReplaceInvalidFileNameChars("consecutive middle//consecutive middle"));
|
||||
Assert.Equal("_leading_middle_trailing_", IOUtil.ReplaceInvalidFileNameChars("/leading/middle/trailing/"));
|
||||
Assert.Equal("__consecutive leading__consecutive middle__consecutive trailing__", IOUtil.ReplaceInvalidFileNameChars("//consecutive leading//consecutive middle//consecutive trailing//"));
|
||||
}
|
||||
|
||||
private static async Task CreateDirectoryReparsePoint(IHostContext context, string link, string target)
|
||||
{
|
||||
#if OS_WINDOWS
|
||||
|
||||
@@ -212,210 +212,5 @@ namespace GitHub.Runner.Common.Tests.Util
|
||||
File.Delete(brokenSymlink);
|
||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, oldValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void UseWhich2FindGit()
|
||||
{
|
||||
using (TestHostContext hc = new(this))
|
||||
{
|
||||
//Arrange
|
||||
Tracing trace = hc.GetTrace();
|
||||
|
||||
// Act.
|
||||
string gitPath = WhichUtil.Which2("git", trace: trace);
|
||||
|
||||
trace.Info($"Which(\"git\") returns: {gitPath ?? string.Empty}");
|
||||
|
||||
// Assert.
|
||||
Assert.True(!string.IsNullOrEmpty(gitPath) && File.Exists(gitPath), $"Unable to find Git through: {nameof(WhichUtil.Which)}");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void Which2ReturnsNullWhenNotFound()
|
||||
{
|
||||
using (TestHostContext hc = new(this))
|
||||
{
|
||||
//Arrange
|
||||
Tracing trace = hc.GetTrace();
|
||||
|
||||
// Act.
|
||||
string nosuch = WhichUtil.Which2("no-such-file-cf7e351f", trace: trace);
|
||||
|
||||
trace.Info($"result: {nosuch ?? string.Empty}");
|
||||
|
||||
// Assert.
|
||||
Assert.True(string.IsNullOrEmpty(nosuch), "Path should not be resolved");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void Which2ThrowsWhenRequireAndNotFound()
|
||||
{
|
||||
using (TestHostContext hc = new(this))
|
||||
{
|
||||
//Arrange
|
||||
Tracing trace = hc.GetTrace();
|
||||
|
||||
// Act.
|
||||
try
|
||||
{
|
||||
WhichUtil.Which2("no-such-file-cf7e351f", require: true, trace: trace);
|
||||
throw new Exception("which should have thrown");
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
Assert.Equal("no-such-file-cf7e351f", ex.FileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void Which2HandleFullyQualifiedPath()
|
||||
{
|
||||
using (TestHostContext hc = new(this))
|
||||
{
|
||||
//Arrange
|
||||
Tracing trace = hc.GetTrace();
|
||||
|
||||
// Act.
|
||||
var gitPath = WhichUtil.Which2("git", require: true, trace: trace);
|
||||
var gitPath2 = WhichUtil.Which2(gitPath, require: true, trace: trace);
|
||||
|
||||
// Assert.
|
||||
Assert.Equal(gitPath, gitPath2);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void Which2HandlesSymlinkToTargetFullPath()
|
||||
{
|
||||
// Arrange
|
||||
using TestHostContext hc = new TestHostContext(this);
|
||||
Tracing trace = hc.GetTrace();
|
||||
string oldValue = Environment.GetEnvironmentVariable(PathUtil.PathVariable);
|
||||
#if OS_WINDOWS
|
||||
string newValue = oldValue + @$";{Path.GetTempPath()}";
|
||||
string symlinkName = $"symlink-{Guid.NewGuid()}";
|
||||
string symlink = Path.GetTempPath() + $"{symlinkName}.exe";
|
||||
string target = Path.GetTempPath() + $"target-{Guid.NewGuid()}.exe";
|
||||
#else
|
||||
string newValue = oldValue + @$":{Path.GetTempPath()}";
|
||||
string symlinkName = $"symlink-{Guid.NewGuid()}";
|
||||
string symlink = Path.GetTempPath() + $"{symlinkName}";
|
||||
string target = Path.GetTempPath() + $"target-{Guid.NewGuid()}";
|
||||
#endif
|
||||
|
||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, newValue);
|
||||
|
||||
|
||||
using (File.Create(target))
|
||||
{
|
||||
File.CreateSymbolicLink(symlink, target);
|
||||
|
||||
// Act.
|
||||
var result = WhichUtil.Which2(symlinkName, require: true, trace: trace);
|
||||
|
||||
// Assert
|
||||
Assert.True(!string.IsNullOrEmpty(result) && File.Exists(result), $"Unable to find symlink through: {nameof(WhichUtil.Which)}");
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Cleanup
|
||||
File.Delete(symlink);
|
||||
File.Delete(target);
|
||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, oldValue);
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void Which2HandlesSymlinkToTargetRelativePath()
|
||||
{
|
||||
// Arrange
|
||||
using TestHostContext hc = new TestHostContext(this);
|
||||
Tracing trace = hc.GetTrace();
|
||||
string oldValue = Environment.GetEnvironmentVariable(PathUtil.PathVariable);
|
||||
#if OS_WINDOWS
|
||||
string newValue = oldValue + @$";{Path.GetTempPath()}";
|
||||
string symlinkName = $"symlink-{Guid.NewGuid()}";
|
||||
string symlink = Path.GetTempPath() + $"{symlinkName}.exe";
|
||||
string targetName = $"target-{Guid.NewGuid()}.exe";
|
||||
string target = Path.GetTempPath() + targetName;
|
||||
#else
|
||||
string newValue = oldValue + @$":{Path.GetTempPath()}";
|
||||
string symlinkName = $"symlink-{Guid.NewGuid()}";
|
||||
string symlink = Path.GetTempPath() + $"{symlinkName}";
|
||||
string targetName = $"target-{Guid.NewGuid()}";
|
||||
string target = Path.GetTempPath() + targetName;
|
||||
#endif
|
||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, newValue);
|
||||
|
||||
|
||||
using (File.Create(target))
|
||||
{
|
||||
File.CreateSymbolicLink(symlink, targetName);
|
||||
|
||||
// Act.
|
||||
var result = WhichUtil.Which2(symlinkName, require: true, trace: trace);
|
||||
|
||||
// Assert
|
||||
Assert.True(!string.IsNullOrEmpty(result) && File.Exists(result), $"Unable to find {symlinkName} through: {nameof(WhichUtil.Which)}");
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
File.Delete(symlink);
|
||||
File.Delete(target);
|
||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, oldValue);
|
||||
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void Which2ThrowsWhenSymlinkBroken()
|
||||
{
|
||||
// Arrange
|
||||
using TestHostContext hc = new TestHostContext(this);
|
||||
Tracing trace = hc.GetTrace();
|
||||
string oldValue = Environment.GetEnvironmentVariable(PathUtil.PathVariable);
|
||||
|
||||
#if OS_WINDOWS
|
||||
string newValue = oldValue + @$";{Path.GetTempPath()}";
|
||||
string brokenSymlinkName = $"broken-symlink-{Guid.NewGuid()}";
|
||||
string brokenSymlink = Path.GetTempPath() + $"{brokenSymlinkName}.exe";
|
||||
#else
|
||||
string newValue = oldValue + @$":{Path.GetTempPath()}";
|
||||
string brokenSymlinkName = $"broken-symlink-{Guid.NewGuid()}";
|
||||
string brokenSymlink = Path.GetTempPath() + $"{brokenSymlinkName}";
|
||||
#endif
|
||||
|
||||
|
||||
string target = "no-such-file-cf7e351f";
|
||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, newValue);
|
||||
|
||||
File.CreateSymbolicLink(brokenSymlink, target);
|
||||
|
||||
// Act.
|
||||
var exception = Assert.Throws<FileNotFoundException>(() => WhichUtil.Which2(brokenSymlinkName, require: true, trace: trace));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(brokenSymlinkName, exception.FileName);
|
||||
|
||||
// Cleanup
|
||||
File.Delete(brokenSymlink);
|
||||
Environment.SetEnvironmentVariable(PathUtil.PathVariable, oldValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
TimelineReference timeline = new();
|
||||
Guid jobId = Guid.NewGuid();
|
||||
string jobName = "some job name";
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
|
||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||
{
|
||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||
|
||||
@@ -382,8 +382,6 @@ runs:
|
||||
}
|
||||
};
|
||||
|
||||
_ec.Object.Global.Variables.Set("DistributedTask.UseActionArchiveCache", bool.TrueString);
|
||||
|
||||
//Act
|
||||
await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
||||
|
||||
@@ -462,7 +460,7 @@ runs:
|
||||
//Act
|
||||
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
||||
|
||||
Assert.True(steps.Count == 0);
|
||||
Assert.Equal(0, steps.Count);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -917,7 +915,7 @@ runs:
|
||||
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
||||
|
||||
// node.js based action doesn't need any extra steps to build/pull containers.
|
||||
Assert.True(steps.Count == 0);
|
||||
Assert.Equal(0, steps.Count);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -1053,7 +1051,7 @@ runs:
|
||||
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
||||
|
||||
// node.js based action doesn't need any extra steps to build/pull containers.
|
||||
Assert.True(steps.Count == 0);
|
||||
Assert.Equal(0, steps.Count);
|
||||
var watermarkFile = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang/runner_L0", "CompositeBasic.completed");
|
||||
Assert.True(File.Exists(watermarkFile));
|
||||
// Comes from the composite action
|
||||
@@ -1247,7 +1245,7 @@ runs:
|
||||
// Assert.
|
||||
Assert.NotNull(definition);
|
||||
Assert.NotNull(definition.Data);
|
||||
Assert.True(definition.Data.Execution.ExecutionType == ActionExecutionType.Script);
|
||||
Assert.Equal(ActionExecutionType.Script, definition.Data.Execution.ExecutionType);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -2375,10 +2373,6 @@ runs:
|
||||
_ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token);
|
||||
_ec.Setup(x => x.Root).Returns(new GitHub.Runner.Worker.ExecutionContext());
|
||||
var variables = new Dictionary<string, VariableValue>();
|
||||
if (enableComposite)
|
||||
{
|
||||
variables["DistributedTask.EnableCompositeActions"] = "true";
|
||||
}
|
||||
_ec.Object.Global.Variables = new Variables(_hc, variables);
|
||||
_ec.Setup(x => x.ExpressionValues).Returns(new DictionaryContextData());
|
||||
_ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
|
||||
|
||||
@@ -193,7 +193,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
TimelineReference timeline = new();
|
||||
Guid jobId = Guid.NewGuid();
|
||||
string jobName = "Summary Job";
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
|
||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||
{
|
||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
TimelineReference timeline = new();
|
||||
Guid jobId = Guid.NewGuid();
|
||||
string jobName = "some job name";
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
|
||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||
{
|
||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||
@@ -106,7 +106,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
TimelineReference timeline = new();
|
||||
Guid jobId = Guid.NewGuid();
|
||||
string jobName = "some job name";
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
|
||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||
{
|
||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||
@@ -162,7 +162,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
TimelineReference timeline = new();
|
||||
Guid jobId = Guid.NewGuid();
|
||||
string jobName = "some job name";
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
|
||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||
{
|
||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||
@@ -216,7 +216,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
TimelineReference timeline = new();
|
||||
Guid jobId = Guid.NewGuid();
|
||||
string jobName = "some job name";
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
|
||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||
{
|
||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||
@@ -271,7 +271,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
TimelineReference timeline = new();
|
||||
Guid jobId = Guid.NewGuid();
|
||||
string jobName = "some job name";
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
|
||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||
{
|
||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||
@@ -322,7 +322,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
TimelineReference timeline = new();
|
||||
Guid jobId = Guid.NewGuid();
|
||||
string jobName = "some job name";
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
|
||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||
{
|
||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||
@@ -373,7 +373,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
TimelineReference timeline = new();
|
||||
Guid jobId = Guid.NewGuid();
|
||||
string jobName = "some job name";
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
|
||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||
{
|
||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||
@@ -471,7 +471,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
TimelineReference timeline = new();
|
||||
Guid jobId = Guid.NewGuid();
|
||||
string jobName = "some job name";
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
|
||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||
{
|
||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||
@@ -555,7 +555,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
TimelineReference timeline = new();
|
||||
Guid jobId = Guid.NewGuid();
|
||||
string jobName = "some job name";
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
|
||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||
{
|
||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||
@@ -610,7 +610,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
TimelineReference timeline = new();
|
||||
Guid jobId = Guid.NewGuid();
|
||||
string jobName = "some job name";
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
|
||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||
{
|
||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||
@@ -653,7 +653,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
TimelineReference timeline = new();
|
||||
Guid jobId = Guid.NewGuid();
|
||||
string jobName = "some job name";
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
|
||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||
{
|
||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||
@@ -717,7 +717,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
TimelineReference timeline = new();
|
||||
Guid jobId = Guid.NewGuid();
|
||||
string jobName = "some job name";
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
|
||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||
{
|
||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||
@@ -781,7 +781,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
TimelineReference timeline = new();
|
||||
Guid jobId = Guid.NewGuid();
|
||||
string jobName = "some job name";
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
|
||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||
{
|
||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||
@@ -969,7 +969,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
TimelineReference timeline = new();
|
||||
Guid jobId = Guid.NewGuid();
|
||||
string jobName = "some job name";
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
|
||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||
{
|
||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||
@@ -1014,7 +1014,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
TimelineReference timeline = new TimelineReference();
|
||||
Guid jobId = Guid.NewGuid();
|
||||
string jobName = "some job name";
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
|
||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||
{
|
||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||
@@ -1057,7 +1057,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
TimelineReference timeline = new TimelineReference();
|
||||
Guid jobId = Guid.NewGuid();
|
||||
string jobName = "some job name";
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
|
||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||
{
|
||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user