mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:36:23 +00:00
Compare commits
47 Commits
v2.316.0
...
users/tihu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
566ff8485a | ||
|
|
9b3b554758 | ||
|
|
4d8402c260 | ||
|
|
aa0ee2bf64 | ||
|
|
dcc64fead2 | ||
|
|
149123c232 | ||
|
|
e292ec220e | ||
|
|
3696b7d89f | ||
|
|
6d7446a45e | ||
|
|
ddf41af767 | ||
|
|
0b0cb5520d | ||
|
|
4c0a43f0e4 | ||
|
|
65764d9ddc | ||
|
|
36c66c8083 | ||
|
|
99b464e102 | ||
|
|
e1fa1fcbc3 | ||
|
|
2979fbad94 | ||
|
|
a77fe8a53f | ||
|
|
7e84ae0b30 | ||
|
|
fb6d1adb43 | ||
|
|
7303cb5673 | ||
|
|
43d67e46db | ||
|
|
ae04147f96 | ||
|
|
12506842c0 | ||
|
|
2190396357 | ||
|
|
41bc0da6fe | ||
|
|
2a7f327d93 | ||
|
|
dbcaa7cf3d | ||
|
|
8df87a82b0 | ||
|
|
70746ff593 | ||
|
|
054fc2e046 | ||
|
|
ecb732eaf4 | ||
|
|
3dab1f1fb0 | ||
|
|
8f1c723ba0 | ||
|
|
1e74a8137b | ||
|
|
3f28dd845f | ||
|
|
edfdbb9661 | ||
|
|
00888c10f9 | ||
|
|
84b1bea43e | ||
|
|
ce4d7be00f | ||
|
|
bd7235ef62 | ||
|
|
0f15173045 | ||
|
|
76dc3a28c0 | ||
|
|
c67e7f2813 | ||
|
|
54052b94fb | ||
|
|
f2c05de91c | ||
|
|
18803bdff6 |
@@ -4,7 +4,7 @@
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
||||
"ghcr.io/devcontainers/features/dotnet": {
|
||||
"version": "6.0.421"
|
||||
"version": "6.0.425"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "16"
|
||||
|
||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -75,7 +75,7 @@ jobs:
|
||||
# Upload runner package tar.gz/zip as artifact
|
||||
- name: Publish Artifact
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: runner-package-${{ matrix.runtime }}
|
||||
path: |
|
||||
|
||||
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);
|
||||
|
||||
41
.github/workflows/release.yml
vendored
41
.github/workflows/release.yml
vendored
@@ -117,12 +117,11 @@ jobs:
|
||||
working-directory: _package
|
||||
|
||||
# Upload runner package tar.gz/zip as artifact.
|
||||
# Since each package name is unique, so we don't need to put ${{matrix}} info into artifact name
|
||||
- name: Publish Artifact
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: runner-packages
|
||||
name: runner-packages-${{ matrix.runtime }}
|
||||
path: |
|
||||
_package
|
||||
|
||||
@@ -134,10 +133,40 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# Download runner package tar.gz/zip produced by 'build' job
|
||||
- name: Download Artifact
|
||||
uses: actions/download-artifact@v1
|
||||
- name: Download Artifact (win-x64)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: runner-packages
|
||||
name: runner-packages-win-x64
|
||||
path: ./
|
||||
- name: Download Artifact (win-arm64)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: runner-packages-win-arm64
|
||||
path: ./
|
||||
- name: Download Artifact (osx-x64)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: runner-packages-osx-x64
|
||||
path: ./
|
||||
- name: Download Artifact (osx-arm64)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: runner-packages-osx-arm64
|
||||
path: ./
|
||||
- name: Download Artifact (linux-x64)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: runner-packages-linux-x64
|
||||
path: ./
|
||||
- name: Download Artifact (linux-arm)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: runner-packages-linux-arm
|
||||
path: ./
|
||||
- name: Download Artifact (linux-arm64)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: runner-packages-linux-arm64
|
||||
path: ./
|
||||
|
||||
# Create ReleaseNote file
|
||||
|
||||
@@ -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.6.0
|
||||
ARG DOCKER_VERSION=25.0.4
|
||||
ARG BUILDX_VERSION=0.13.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,16 @@ 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 curl jq unzip \
|
||||
&& 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 \
|
||||
&& apt install -y --no-install-recommends git
|
||||
|
||||
RUN adduser --disabled-password --gecos "" --uid 1001 runner \
|
||||
&& groupadd docker --gid 123 \
|
||||
&& usermod -aG sudo runner \
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
## What's Changed
|
||||
* Load '_runnerSettings' in the early point of JobRunner.cs by @TingluoHuang in https://github.com/actions/runner/pull/3218
|
||||
* Add new SessionConflict return code by @eeSquared in https://github.com/actions/runner/pull/3215
|
||||
* backoff if we retried polling for more than 50 times in less than 30minutes by @aiqiaoy in https://github.com/actions/runner/pull/3232
|
||||
* Update dotnet sdk to latest version @6.0.421 by @github-actions in https://github.com/actions/runner/pull/3244
|
||||
* Cleanup enabled feature flags. by @TingluoHuang in https://github.com/actions/runner/pull/3246
|
||||
* Relax the condition to stop uploading to Results by @yacaovsnc in https://github.com/actions/runner/pull/3230
|
||||
* Cleanup enabled feature flags. by @TingluoHuang in https://github.com/actions/runner/pull/3248
|
||||
* Replace invalid file name chars in diag log name by @ericsciple in https://github.com/actions/runner/pull/3249
|
||||
|
||||
## New Contributors
|
||||
* @eeSquared made their first contribution in https://github.com/actions/runner/pull/3215
|
||||
* @aiqiaoy made their first contribution in https://github.com/actions/runner/pull/3232
|
||||
- Adding Snapshot additional mapping tokens https://github.com/actions/runner/pull/3468
|
||||
- Create launch httpclient using the right handler and setting https://github.com/actions/runner/pull/3476
|
||||
- Fix missing default user-agent for jitconfig runner https://github.com/actions/runner/pull/3473
|
||||
- Cleanup back-compat code for interpreting Run Service status codes https://github.com/actions/runner/pull/3456
|
||||
- Add runner or worker to the useragent https://github.com/actions/runner/pull/3457
|
||||
- Handle Error Body in Responses from Broker https://github.com/actions/runner/pull/3454
|
||||
- Fix issues for composite actions (Run Service flow) https://github.com/actions/runner/pull/3446
|
||||
- Trace GitHub RequestId to log https://github.com/actions/runner/pull/3442
|
||||
- Add `jq`, `git`, `unzip` and `curl` to default packages installed https://github.com/actions/runner/pull/3056
|
||||
- Add pid to user-agent and session owner https://github.com/actions/runner/pull/3432
|
||||
|
||||
**Full Changelog**: https://github.com/actions/runner/compare/v2.315.0...v2.316.0
|
||||
**Full Changelog**: https://github.com/actions/runner/compare/v2.319.1...v2.320.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
|
||||
@@ -33,12 +34,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
|
||||
@@ -50,7 +53,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
|
||||
@@ -61,7 +64,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
|
||||
@@ -72,7 +75,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
|
||||
@@ -83,7 +86,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
|
||||
@@ -94,7 +97,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
|
||||
@@ -104,6 +107,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 +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
|
||||
|
||||
1
src/Misc/layoutroot/mock_update_messages.json
Normal file
1
src/Misc/layoutroot/mock_update_messages.json
Normal file
@@ -0,0 +1 @@
|
||||
{ "2.319.0": {"targetVersion":"2.320.0"}}
|
||||
@@ -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;
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace GitHub.Runner.Common
|
||||
|
||||
public bool ShouldRetryException(Exception ex)
|
||||
{
|
||||
if (ex is AccessDeniedException ade && ade.ErrorCode == 1)
|
||||
if (ex is AccessDeniedException ade)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ 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 uses Node.js version which is deprecated 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 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";
|
||||
|
||||
}
|
||||
@@ -280,6 +280,10 @@ namespace GitHub.Runner.Common
|
||||
public static readonly string PhaseDisplayName = "system.phaseDisplayName";
|
||||
public static readonly string JobRequestType = "system.jobRequestType";
|
||||
public static readonly string OrchestrationId = "system.orchestrationId";
|
||||
public static readonly string TestDotNet8Compatibility = "system.testDotNet8Compatibility";
|
||||
public static readonly string DotNet8CompatibilityOutputLength = "system.dotNet8CompatibilityOutputLength";
|
||||
public static readonly string DotNet8CompatibilityOutputPattern = "system.dotNet8CompatibilityOutputPattern";
|
||||
public static readonly string DotNet8CompatibilityWarning = "system.dotNet8CompatibilityWarning";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace GitHub.Runner.Common
|
||||
event EventHandler Unloading;
|
||||
void ShutdownRunner(ShutdownReason reason);
|
||||
void WritePerfCounter(string counter);
|
||||
void LoadDefaultUserAgents();
|
||||
}
|
||||
|
||||
public enum StartupType
|
||||
@@ -67,6 +68,7 @@ namespace GitHub.Runner.Common
|
||||
private StartupType _startupType;
|
||||
private string _perfFile;
|
||||
private RunnerWebProxy _webProxy = new();
|
||||
private string _hostType = string.Empty;
|
||||
|
||||
public event EventHandler Unloading;
|
||||
public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token;
|
||||
@@ -78,6 +80,7 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
// Validate args.
|
||||
ArgUtil.NotNullOrEmpty(hostType, nameof(hostType));
|
||||
_hostType = hostType;
|
||||
|
||||
_loadContext = AssemblyLoadContext.GetLoadContext(typeof(HostContext).GetTypeInfo().Assembly);
|
||||
_loadContext.Unloading += LoadContext_Unloading;
|
||||
@@ -196,6 +199,16 @@ namespace GitHub.Runner.Common
|
||||
}
|
||||
}
|
||||
|
||||
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY")))
|
||||
{
|
||||
_trace.Warning($"Runner is running under insecure mode: HTTPS server certificate validation has been turned off by GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY environment variable.");
|
||||
}
|
||||
|
||||
LoadDefaultUserAgents();
|
||||
}
|
||||
|
||||
public void LoadDefaultUserAgents()
|
||||
{
|
||||
if (string.IsNullOrEmpty(WebProxy.HttpProxyAddress) && string.IsNullOrEmpty(WebProxy.HttpsProxyAddress))
|
||||
{
|
||||
_trace.Info($"No proxy settings were found based on environmental variables (http_proxy/https_proxy/HTTP_PROXY/HTTPS_PROXY)");
|
||||
@@ -205,11 +218,6 @@ namespace GitHub.Runner.Common
|
||||
_userAgents.Add(new ProductInfoHeaderValue("HttpProxyConfigured", bool.TrueString));
|
||||
}
|
||||
|
||||
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY")))
|
||||
{
|
||||
_trace.Warning($"Runner is running under insecure mode: HTTPS server certificate validation has been turned off by GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY environment variable.");
|
||||
}
|
||||
|
||||
var credFile = GetConfigFile(WellKnownConfigFile.Credentials);
|
||||
if (File.Exists(credFile))
|
||||
{
|
||||
@@ -244,6 +252,11 @@ namespace GitHub.Runner.Common
|
||||
_trace.Info($"Adding extra user agent '{extraUserAgentHeader}' to all HTTP requests.");
|
||||
_userAgents.Add(extraUserAgentHeader);
|
||||
}
|
||||
|
||||
var currentProcess = Process.GetCurrentProcess();
|
||||
_userAgents.Add(new ProductInfoHeaderValue("Pid", currentProcess.Id.ToString()));
|
||||
_userAgents.Add(new ProductInfoHeaderValue("CreationTime", Uri.EscapeDataString(DateTime.UtcNow.ToString("O"))));
|
||||
_userAgents.Add(new ProductInfoHeaderValue($"({_hostType})"));
|
||||
}
|
||||
|
||||
public string GetDirectory(WellKnownDirectory directory)
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Security;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
@@ -179,6 +180,10 @@ namespace GitHub.Runner.Common
|
||||
userAgentValues.AddRange(UserAgentUtility.GetDefaultRestUserAgent());
|
||||
userAgentValues.AddRange(HostContext.UserAgents);
|
||||
this._websocketClient.Options.SetRequestHeader("User-Agent", string.Join(" ", userAgentValues.Select(x => x.ToString())));
|
||||
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY")))
|
||||
{
|
||||
this._websocketClient.Options.RemoteCertificateValidationCallback = (_, _, _, _) => true;
|
||||
}
|
||||
|
||||
this._websocketConnectTask = ConnectWebSocketClient(feedStreamUrl, delay);
|
||||
}
|
||||
|
||||
@@ -613,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()
|
||||
@@ -709,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);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.Launch.Client;
|
||||
using GitHub.Services.WebApi;
|
||||
|
||||
namespace GitHub.Runner.Common
|
||||
{
|
||||
@@ -23,8 +24,21 @@ namespace GitHub.Runner.Common
|
||||
|
||||
public void InitializeLaunchClient(Uri uri, string token)
|
||||
{
|
||||
var httpMessageHandler = HostContext.CreateHttpClientHandler();
|
||||
this._launchClient = new LaunchHttpClient(uri, httpMessageHandler, token, disposeHandler: true);
|
||||
// Using default 100 timeout
|
||||
RawClientHttpRequestSettings settings = VssUtil.GetHttpRequestSettings(null);
|
||||
|
||||
// Create retry handler
|
||||
IEnumerable<DelegatingHandler> delegatingHandlers = new List<DelegatingHandler>();
|
||||
if (settings.MaxRetryRequest > 0)
|
||||
{
|
||||
delegatingHandlers = new DelegatingHandler[] { new VssHttpRetryMessageHandler(settings.MaxRetryRequest) };
|
||||
}
|
||||
|
||||
// Setup RawHttpMessageHandler without credentials
|
||||
var httpMessageHandler = new RawHttpMessageHandler(new NoOpCredentials(null), settings);
|
||||
var pipeline = HttpClientFactory.CreatePipeline(httpMessageHandler, delegatingHandlers);
|
||||
|
||||
this._launchClient = new LaunchHttpClient(uri, pipeline, token, disposeHandler: true);
|
||||
}
|
||||
|
||||
public Task<ActionDownloadInfoCollection> ResolveActionsDownloadInfoAsync(Guid planId, Guid jobId, ActionReferenceList actionReferenceList,
|
||||
|
||||
@@ -62,7 +62,10 @@ namespace GitHub.Runner.Common
|
||||
CheckConnection();
|
||||
return RetryRequest<AgentJobRequestMessage>(
|
||||
async () => await _runServiceHttpClient.GetJobMessageAsync(requestUri, id, VarUtil.OS, cancellationToken), cancellationToken,
|
||||
shouldRetry: ex => ex is not TaskOrchestrationJobAlreadyAcquiredException);
|
||||
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(
|
||||
|
||||
@@ -69,7 +69,8 @@ namespace GitHub.Runner.Listener
|
||||
Version = BuildConstants.RunnerPackage.Version,
|
||||
OSDescription = RuntimeInformation.OSDescription,
|
||||
};
|
||||
string sessionName = $"{Environment.MachineName ?? "RUNNER"}";
|
||||
var currentProcess = Process.GetCurrentProcess();
|
||||
string sessionName = $"{Environment.MachineName ?? "RUNNER"} (PID: {currentProcess.Id})";
|
||||
var taskAgentSession = new TaskAgentSession(sessionName, agent);
|
||||
|
||||
string errorMessage = string.Empty;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,8 @@ namespace GitHub.Runner.Listener
|
||||
Version = BuildConstants.RunnerPackage.Version,
|
||||
OSDescription = RuntimeInformation.OSDescription,
|
||||
};
|
||||
string sessionName = $"{Environment.MachineName ?? "RUNNER"}";
|
||||
var currentProcess = Process.GetCurrentProcess();
|
||||
string sessionName = $"{Environment.MachineName ?? "RUNNER"} (PID: {currentProcess.Id})";
|
||||
var taskAgentSession = new TaskAgentSession(sessionName, agent);
|
||||
|
||||
string errorMessage = string.Empty;
|
||||
@@ -247,15 +248,16 @@ namespace GitHub.Runner.Listener
|
||||
_getMessagesTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
|
||||
try
|
||||
{
|
||||
message = await _runnerServer.GetAgentMessageAsync(_settings.PoolId,
|
||||
_session.SessionId,
|
||||
_lastMessageId,
|
||||
runnerStatus,
|
||||
BuildConstants.RunnerPackage.Version,
|
||||
VarUtil.OS,
|
||||
VarUtil.OSArchitecture,
|
||||
_settings.DisableUpdate,
|
||||
_getMessagesTokenSource.Token);
|
||||
await Task.Delay(5000);
|
||||
// message = await _runnerServer.GetAgentMessageAsync(_settings.PoolId,
|
||||
// _session.SessionId,
|
||||
// _lastMessageId,
|
||||
// runnerStatus,
|
||||
// BuildConstants.RunnerPackage.Version,
|
||||
// VarUtil.OS,
|
||||
// VarUtil.OSArchitecture,
|
||||
// _settings.DisableUpdate,
|
||||
// _getMessagesTokenSource.Token);
|
||||
|
||||
// Decrypt the message body if the session is using encryption
|
||||
message = DecryptMessage(message);
|
||||
@@ -263,8 +265,6 @@ namespace GitHub.Runner.Listener
|
||||
|
||||
if (message != null && message.MessageType == BrokerMigrationMessage.MessageType)
|
||||
{
|
||||
Trace.Info("BrokerMigration message received. Polling Broker for messages...");
|
||||
|
||||
var migrationMessage = JsonUtility.FromString<BrokerMigrationMessage>(message.Body);
|
||||
|
||||
await _brokerServer.UpdateConnectionIfNeeded(migrationMessage.BrokerBaseUrl, _creds);
|
||||
|
||||
@@ -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)
|
||||
@@ -222,6 +237,10 @@ namespace GitHub.Runner.Listener
|
||||
File.SetAttributes(configFile, File.GetAttributes(configFile) | FileAttributes.Hidden);
|
||||
Trace.Info($"Saved {configContent.Length} bytes to '{configFile}'.");
|
||||
}
|
||||
|
||||
// make sure we have the right user agent data added from the jitconfig
|
||||
HostContext.LoadDefaultUserAgents();
|
||||
VssUtil.InitializeVssClientSettings(HostContext.UserAgents, HostContext.WebProxy);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -460,14 +479,14 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
}
|
||||
|
||||
message = await getNextMessage; //get next message
|
||||
HostContext.WritePerfCounter($"MessageReceived_{message.MessageType}");
|
||||
if (string.Equals(message.MessageType, AgentRefreshMessage.MessageType, StringComparison.OrdinalIgnoreCase))
|
||||
// message = await getNextMessage; //get next message
|
||||
// HostContext.WritePerfCounter($"MessageReceived_{message.MessageType}");
|
||||
// if (string.Equals(message.MessageType, AgentRefreshMessage.MessageType, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (autoUpdateInProgress == false)
|
||||
{
|
||||
autoUpdateInProgress = true;
|
||||
AgentRefreshMessage runnerUpdateMessage = JsonUtility.FromString<AgentRefreshMessage>(message.Body);
|
||||
AgentRefreshMessage runnerUpdateMessage = null;
|
||||
|
||||
#if DEBUG
|
||||
// Can mock the update for testing
|
||||
@@ -483,9 +502,9 @@ namespace GitHub.Runner.Listener
|
||||
if (mockUpdateMessages.ContainsKey(BuildConstants.RunnerPackage.Version))
|
||||
{
|
||||
var mockTargetVersion = mockUpdateMessages[BuildConstants.RunnerPackage.Version].TargetVersion;
|
||||
_term.WriteLine($"Mocking update, using version {mockTargetVersion} instead of {runnerUpdateMessage.TargetVersion}");
|
||||
Trace.Info($"Mocking update, using version {mockTargetVersion} instead of {runnerUpdateMessage.TargetVersion}");
|
||||
runnerUpdateMessage = new AgentRefreshMessage(runnerUpdateMessage.AgentId, mockTargetVersion, runnerUpdateMessage.Timeout);
|
||||
_term.WriteLine($"Mocking update, using version {mockTargetVersion}");
|
||||
Trace.Info($"Mocking update, using version {mockTargetVersion}");
|
||||
runnerUpdateMessage = new AgentRefreshMessage(settings.AgentId, mockTargetVersion, TimeSpan.FromSeconds(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -499,122 +518,125 @@ namespace GitHub.Runner.Listener
|
||||
Trace.Info("Refresh message received, skip autoupdate since a previous autoupdate is already running.");
|
||||
}
|
||||
}
|
||||
else if (string.Equals(message.MessageType, RunnerRefreshMessage.MessageType, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (autoUpdateInProgress == false)
|
||||
{
|
||||
autoUpdateInProgress = true;
|
||||
RunnerRefreshMessage brokerRunnerUpdateMessage = JsonUtility.FromString<RunnerRefreshMessage>(message.Body);
|
||||
// else if (string.Equals(message.MessageType, RunnerRefreshMessage.MessageType, StringComparison.OrdinalIgnoreCase))
|
||||
// {
|
||||
// if (autoUpdateInProgress == false)
|
||||
// {
|
||||
// autoUpdateInProgress = true;
|
||||
// RunnerRefreshMessage brokerRunnerUpdateMessage = JsonUtility.FromString<RunnerRefreshMessage>(message.Body);
|
||||
|
||||
var selfUpdater = HostContext.GetService<ISelfUpdaterV2>();
|
||||
selfUpdateTask = selfUpdater.SelfUpdate(brokerRunnerUpdateMessage, jobDispatcher, false, HostContext.RunnerShutdownToken);
|
||||
Trace.Info("Refresh message received, kick-off selfupdate background process.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace.Info("Refresh message received, skip autoupdate since a previous autoupdate is already running.");
|
||||
}
|
||||
}
|
||||
else if (string.Equals(message.MessageType, JobRequestMessageTypes.PipelineAgentJobRequest, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (autoUpdateInProgress || runOnceJobReceived)
|
||||
{
|
||||
skipMessageDeletion = true;
|
||||
Trace.Info($"Skip message deletion for job request message '{message.MessageId}'.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace.Info($"Received job message of length {message.Body.Length} from service, with hash '{IOUtil.GetSha256Hash(message.Body)}'");
|
||||
var jobMessage = StringUtil.ConvertFromJson<Pipelines.AgentJobRequestMessage>(message.Body);
|
||||
jobDispatcher.Run(jobMessage, runOnce);
|
||||
if (runOnce)
|
||||
{
|
||||
Trace.Info("One time used runner received job message.");
|
||||
runOnceJobReceived = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Broker flow
|
||||
else if (MessageUtil.IsRunServiceJob(message.MessageType))
|
||||
{
|
||||
if (autoUpdateInProgress || runOnceJobReceived)
|
||||
{
|
||||
skipMessageDeletion = true;
|
||||
Trace.Info($"Skip message deletion for job request message '{message.MessageId}'.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var messageRef = StringUtil.ConvertFromJson<RunnerJobRequestRef>(message.Body);
|
||||
Pipelines.AgentJobRequestMessage jobRequestMessage = null;
|
||||
// var selfUpdater = HostContext.GetService<ISelfUpdaterV2>();
|
||||
// selfUpdateTask = selfUpdater.SelfUpdate(brokerRunnerUpdateMessage, jobDispatcher, false, HostContext.RunnerShutdownToken);
|
||||
// Trace.Info("Refresh message received, kick-off selfupdate background process.");
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Trace.Info("Refresh message received, skip autoupdate since a previous autoupdate is already running.");
|
||||
// }
|
||||
// }
|
||||
// else if (string.Equals(message.MessageType, JobRequestMessageTypes.PipelineAgentJobRequest, StringComparison.OrdinalIgnoreCase))
|
||||
// {
|
||||
// if (autoUpdateInProgress || runOnceJobReceived)
|
||||
// {
|
||||
// skipMessageDeletion = true;
|
||||
// Trace.Info($"Skip message deletion for job request message '{message.MessageId}'.");
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Trace.Info($"Received job message of length {message.Body.Length} from service, with hash '{IOUtil.GetSha256Hash(message.Body)}'");
|
||||
// var jobMessage = StringUtil.ConvertFromJson<Pipelines.AgentJobRequestMessage>(message.Body);
|
||||
// jobDispatcher.Run(jobMessage, runOnce);
|
||||
// if (runOnce)
|
||||
// {
|
||||
// Trace.Info("One time used runner received job message.");
|
||||
// runOnceJobReceived = true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// // Broker flow
|
||||
// else if (MessageUtil.IsRunServiceJob(message.MessageType))
|
||||
// {
|
||||
// if (autoUpdateInProgress || runOnceJobReceived)
|
||||
// {
|
||||
// skipMessageDeletion = true;
|
||||
// Trace.Info($"Skip message deletion for job request message '{message.MessageId}'.");
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// var messageRef = StringUtil.ConvertFromJson<RunnerJobRequestRef>(message.Body);
|
||||
// Pipelines.AgentJobRequestMessage jobRequestMessage = null;
|
||||
|
||||
// Create connection
|
||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
||||
var creds = credMgr.LoadCredentials();
|
||||
// // Create connection
|
||||
// var credMgr = HostContext.GetService<ICredentialManager>();
|
||||
// var creds = credMgr.LoadCredentials();
|
||||
|
||||
if (string.IsNullOrEmpty(messageRef.RunServiceUrl))
|
||||
{
|
||||
var actionsRunServer = HostContext.CreateService<IActionsRunServer>();
|
||||
await actionsRunServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
||||
jobRequestMessage = await actionsRunServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
||||
}
|
||||
else
|
||||
{
|
||||
var runServer = HostContext.CreateService<IRunServer>();
|
||||
await runServer.ConnectAsync(new Uri(messageRef.RunServiceUrl), creds);
|
||||
try
|
||||
{
|
||||
jobRequestMessage =
|
||||
await runServer.GetJobMessageAsync(messageRef.RunnerRequestId,
|
||||
messageQueueLoopTokenSource.Token);
|
||||
}
|
||||
catch (TaskOrchestrationJobAlreadyAcquiredException)
|
||||
{
|
||||
Trace.Info("Job is already acquired, skip this message.");
|
||||
continue;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error($"Caught exception from acquiring job message: {ex}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// if (string.IsNullOrEmpty(messageRef.RunServiceUrl))
|
||||
// {
|
||||
// var actionsRunServer = HostContext.CreateService<IActionsRunServer>();
|
||||
// await actionsRunServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
||||
// jobRequestMessage = await actionsRunServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// var runServer = HostContext.CreateService<IRunServer>();
|
||||
// await runServer.ConnectAsync(new Uri(messageRef.RunServiceUrl), creds);
|
||||
// try
|
||||
// {
|
||||
// jobRequestMessage = await runServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
||||
// _acquireJobThrottler.Reset();
|
||||
// }
|
||||
// catch (Exception ex) when (
|
||||
// ex is TaskOrchestrationJobNotFoundException || // HTTP status 404
|
||||
// ex is TaskOrchestrationJobAlreadyAcquiredException || // HTTP status 409
|
||||
// ex is TaskOrchestrationJobUnprocessableException) // HTTP status 422
|
||||
// {
|
||||
// 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;
|
||||
// }
|
||||
// }
|
||||
|
||||
jobDispatcher.Run(jobRequestMessage, runOnce);
|
||||
if (runOnce)
|
||||
{
|
||||
Trace.Info("One time used runner received job message.");
|
||||
runOnceJobReceived = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (string.Equals(message.MessageType, JobCancelMessage.MessageType, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var cancelJobMessage = JsonUtility.FromString<JobCancelMessage>(message.Body);
|
||||
bool jobCancelled = jobDispatcher.Cancel(cancelJobMessage);
|
||||
skipMessageDeletion = (autoUpdateInProgress || runOnceJobReceived) && !jobCancelled;
|
||||
// jobDispatcher.Run(jobRequestMessage, runOnce);
|
||||
// if (runOnce)
|
||||
// {
|
||||
// Trace.Info("One time used runner received job message.");
|
||||
// runOnceJobReceived = true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// else if (string.Equals(message.MessageType, JobCancelMessage.MessageType, StringComparison.OrdinalIgnoreCase))
|
||||
// {
|
||||
// var cancelJobMessage = JsonUtility.FromString<JobCancelMessage>(message.Body);
|
||||
// bool jobCancelled = jobDispatcher.Cancel(cancelJobMessage);
|
||||
// skipMessageDeletion = (autoUpdateInProgress || runOnceJobReceived) && !jobCancelled;
|
||||
|
||||
if (skipMessageDeletion)
|
||||
{
|
||||
Trace.Info($"Skip message deletion for cancellation message '{message.MessageId}'.");
|
||||
}
|
||||
}
|
||||
else if (string.Equals(message.MessageType, Pipelines.HostedRunnerShutdownMessage.MessageType, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var HostedRunnerShutdownMessage = JsonUtility.FromString<Pipelines.HostedRunnerShutdownMessage>(message.Body);
|
||||
skipMessageDeletion = true;
|
||||
skipSessionDeletion = true;
|
||||
Trace.Info($"Service requests the hosted runner to shutdown. Reason: '{HostedRunnerShutdownMessage.Reason}'.");
|
||||
return Constants.Runner.ReturnCode.Success;
|
||||
}
|
||||
else if (string.Equals(message.MessageType, TaskAgentMessageTypes.ForceTokenRefresh))
|
||||
{
|
||||
Trace.Info("Received ForceTokenRefreshMessage");
|
||||
await _listener.RefreshListenerTokenAsync(messageQueueLoopTokenSource.Token);
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace.Error($"Received message {message.MessageId} with unsupported message type {message.MessageType}.");
|
||||
}
|
||||
// if (skipMessageDeletion)
|
||||
// {
|
||||
// Trace.Info($"Skip message deletion for cancellation message '{message.MessageId}'.");
|
||||
// }
|
||||
// }
|
||||
// else if (string.Equals(message.MessageType, Pipelines.HostedRunnerShutdownMessage.MessageType, StringComparison.OrdinalIgnoreCase))
|
||||
// {
|
||||
// var HostedRunnerShutdownMessage = JsonUtility.FromString<Pipelines.HostedRunnerShutdownMessage>(message.Body);
|
||||
// skipMessageDeletion = true;
|
||||
// skipSessionDeletion = true;
|
||||
// Trace.Info($"Service requests the hosted runner to shutdown. Reason: '{HostedRunnerShutdownMessage.Reason}'.");
|
||||
// return Constants.Runner.ReturnCode.Success;
|
||||
// }
|
||||
// else if (string.Equals(message.MessageType, TaskAgentMessageTypes.ForceTokenRefresh))
|
||||
// {
|
||||
// Trace.Info("Received ForceTokenRefreshMessage");
|
||||
// await _listener.RefreshListenerTokenAsync(messageQueueLoopTokenSource.Token);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Trace.Error($"Received message {message.MessageId} with unsupported message type {message.MessageType}.");
|
||||
// }
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -132,26 +132,39 @@ namespace GitHub.Runner.Listener
|
||||
|
||||
private async Task<bool> UpdateNeeded(string targetVersion, CancellationToken token)
|
||||
{
|
||||
// when talk to old version server, always prefer latest package.
|
||||
// old server won't send target version as part of update message.
|
||||
if (string.IsNullOrEmpty(targetVersion))
|
||||
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_IS_MOCK_UPDATE")))
|
||||
{
|
||||
var packages = await _runnerServer.GetPackagesAsync(_packageType, _platform, 1, true, token);
|
||||
if (packages == null || packages.Count == 0)
|
||||
_targetPackage = new PackageMetadata()
|
||||
{
|
||||
Trace.Info($"There is no package for {_packageType} and {_platform}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
_targetPackage = packages.FirstOrDefault();
|
||||
Type = "agent",
|
||||
Platform = BuildConstants.RunnerPackage.PackageName,
|
||||
Version = new PackageVersion(targetVersion),
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
_targetPackage = await _runnerServer.GetPackageAsync(_packageType, _platform, targetVersion, true, token);
|
||||
if (_targetPackage == null)
|
||||
|
||||
// when talk to old version server, always prefer latest package.
|
||||
// old server won't send target version as part of update message.
|
||||
if (string.IsNullOrEmpty(targetVersion))
|
||||
{
|
||||
Trace.Info($"There is no package for {_packageType} and {_platform} with version {targetVersion}.");
|
||||
return false;
|
||||
var packages = await _runnerServer.GetPackagesAsync(_packageType, _platform, 1, true, token);
|
||||
if (packages == null || packages.Count == 0)
|
||||
{
|
||||
Trace.Info($"There is no package for {_packageType} and {_platform}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
_targetPackage = packages.FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
_targetPackage = await _runnerServer.GetPackageAsync(_packageType, _platform, targetVersion, true, token);
|
||||
if (_targetPackage == null)
|
||||
{
|
||||
Trace.Info($"There is no package for {_packageType} and {_platform} with version {targetVersion}.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1102,6 +1102,7 @@ namespace GitHub.Runner.Worker
|
||||
int timeoutSeconds = 20 * 60;
|
||||
while (retryCount < 3)
|
||||
{
|
||||
string requestId = string.Empty;
|
||||
using (var actionDownloadTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds)))
|
||||
using (var actionDownloadCancellation = CancellationTokenSource.CreateLinkedTokenSource(actionDownloadTimeout.Token, executionContext.CancellationToken))
|
||||
{
|
||||
@@ -1117,7 +1118,7 @@ namespace GitHub.Runner.Worker
|
||||
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
||||
using (var response = await httpClient.GetAsync(downloadUrl))
|
||||
{
|
||||
var requestId = UrlUtil.GetGitHubRequestId(response.Headers);
|
||||
requestId = UrlUtil.GetGitHubRequestId(response.Headers);
|
||||
if (!string.IsNullOrEmpty(requestId))
|
||||
{
|
||||
Trace.Info($"Request URL: {downloadUrl} X-GitHub-Request-Id: {requestId} Http Status: {response.StatusCode}");
|
||||
@@ -1155,7 +1156,7 @@ namespace GitHub.Runner.Worker
|
||||
catch (OperationCanceledException ex) when (!executionContext.CancellationToken.IsCancellationRequested && retryCount >= 2)
|
||||
{
|
||||
Trace.Info($"Action download final retry timeout after {timeoutSeconds} seconds.");
|
||||
throw new TimeoutException($"Action '{downloadUrl}' download has timed out. Error: {ex.Message}");
|
||||
throw new TimeoutException($"Action '{downloadUrl}' download has timed out. Error: {ex.Message} {requestId}");
|
||||
}
|
||||
catch (ActionNotFoundException)
|
||||
{
|
||||
@@ -1170,11 +1171,11 @@ namespace GitHub.Runner.Worker
|
||||
if (actionDownloadTimeout.Token.IsCancellationRequested)
|
||||
{
|
||||
// action download didn't finish within timeout
|
||||
executionContext.Warning($"Action '{downloadUrl}' didn't finish download within {timeoutSeconds} seconds.");
|
||||
executionContext.Warning($"Action '{downloadUrl}' didn't finish download within {timeoutSeconds} seconds. {requestId}");
|
||||
}
|
||||
else
|
||||
{
|
||||
executionContext.Warning($"Failed to download action '{downloadUrl}'. Error: {ex.Message}");
|
||||
executionContext.Warning($"Failed to download action '{downloadUrl}'. Error: {ex.Message} {requestId}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace GitHub.Runner.Worker
|
||||
// Initialize
|
||||
void InitializeJob(Pipelines.AgentJobRequestMessage message, CancellationToken token);
|
||||
void CancelToken();
|
||||
IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, ActionRunStage stage, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null, Guid embeddedId = default(Guid), string siblingScopeName = null, TimeSpan? timeout = null);
|
||||
IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, ActionRunStage stage, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, List<Issue> embeddedIssueCollector = null, CancellationTokenSource cancellationTokenSource = null, Guid embeddedId = default(Guid), string siblingScopeName = null, TimeSpan? timeout = null);
|
||||
IExecutionContext CreateEmbeddedChild(string scopeName, string contextName, Guid embeddedId, ActionRunStage stage, Dictionary<string, string> intraActionState = null, string siblingScopeName = null);
|
||||
|
||||
// logging
|
||||
@@ -135,7 +135,6 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
private readonly TimelineRecord _record = new();
|
||||
private readonly Dictionary<Guid, TimelineRecord> _detailRecords = new();
|
||||
private readonly List<Issue> _embeddedIssueCollector;
|
||||
private readonly object _loggerLock = new();
|
||||
private readonly object _matchersLock = new();
|
||||
private readonly ExecutionContext _parentExecutionContext;
|
||||
@@ -154,6 +153,7 @@ namespace GitHub.Runner.Worker
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private TaskCompletionSource<int> _forceCompleted = new();
|
||||
private bool _throttlingReported = false;
|
||||
private List<Issue> _embeddedIssueCollector;
|
||||
|
||||
// only job level ExecutionContext will track throttling delay.
|
||||
private long _totalThrottlingDelayInMilliseconds = 0;
|
||||
@@ -356,6 +356,7 @@ namespace GitHub.Runner.Worker
|
||||
int? recordOrder = null,
|
||||
IPagingLogger logger = null,
|
||||
bool isEmbedded = false,
|
||||
List<Issue> embeddedIssueCollector = null,
|
||||
CancellationTokenSource cancellationTokenSource = null,
|
||||
Guid embeddedId = default(Guid),
|
||||
string siblingScopeName = null,
|
||||
@@ -365,6 +366,10 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
var child = new ExecutionContext(this, isEmbedded);
|
||||
child.Initialize(HostContext);
|
||||
if ((Global.Variables.GetBoolean("RunService.FixEmbeddedIssues") ?? false) && embeddedIssueCollector != null)
|
||||
{
|
||||
child._embeddedIssueCollector = embeddedIssueCollector;
|
||||
}
|
||||
child.Global = Global;
|
||||
child.ScopeName = scopeName;
|
||||
child.ContextName = contextName;
|
||||
@@ -433,7 +438,7 @@ namespace GitHub.Runner.Worker
|
||||
Dictionary<string, string> intraActionState = null,
|
||||
string siblingScopeName = null)
|
||||
{
|
||||
return Root.CreateChild(_record.Id, _record.Name, _record.Id.ToString("N"), scopeName, contextName, stage, logger: _logger, isEmbedded: true, cancellationTokenSource: null, intraActionState: intraActionState, embeddedId: embeddedId, siblingScopeName: siblingScopeName, timeout: GetRemainingTimeout(), recordOrder: _record.Order);
|
||||
return Root.CreateChild(_record.Id, _record.Name, _record.Id.ToString("N"), scopeName, contextName, stage, logger: _logger, isEmbedded: true, embeddedIssueCollector: _embeddedIssueCollector, cancellationTokenSource: null, intraActionState: intraActionState, embeddedId: embeddedId, siblingScopeName: siblingScopeName, timeout: GetRemainingTimeout(), recordOrder: _record.Order);
|
||||
}
|
||||
|
||||
public void Start(string currentOperation = null)
|
||||
@@ -503,6 +508,8 @@ namespace GitHub.Runner.Worker
|
||||
Status = _record.State,
|
||||
Number = _record.Order,
|
||||
Name = _record.Name,
|
||||
Ref = StepTelemetry?.Ref,
|
||||
Type = StepTelemetry?.Type,
|
||||
StartedAt = _record.StartTime,
|
||||
CompletedAt = _record.FinishTime,
|
||||
Annotations = new List<Annotation>()
|
||||
@@ -520,7 +527,6 @@ namespace GitHub.Runner.Worker
|
||||
Global.StepsResult.Add(stepResult);
|
||||
}
|
||||
|
||||
|
||||
if (Root != this)
|
||||
{
|
||||
// only dispose TokenSource for step level ExecutionContext
|
||||
@@ -837,7 +843,6 @@ namespace GitHub.Runner.Worker
|
||||
// Actions environment
|
||||
ActionsEnvironment = message.ActionsEnvironment;
|
||||
|
||||
|
||||
// Service container info
|
||||
Global.ServiceContainers = new List<ContainerInfo>();
|
||||
|
||||
@@ -1418,7 +1423,7 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
if (key == PipelineTemplateConstants.HostWorkspace)
|
||||
{
|
||||
// The HostWorkspace context var is excluded so that there is a var that always points to the host path.
|
||||
// The HostWorkspace context var is excluded so that there is a var that always points to the host path.
|
||||
// This var can be used to translate back from container paths, e.g. in HashFilesFunction, which always runs on the host machine
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -93,7 +93,6 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
ExecutionContext.StepTelemetry.HasPreStep = Data.HasPre;
|
||||
ExecutionContext.StepTelemetry.HasPostStep = Data.HasPost;
|
||||
}
|
||||
ExecutionContext.StepTelemetry.Type = Data.NodeVersion;
|
||||
|
||||
ArgUtil.NotNullOrEmpty(target, nameof(target));
|
||||
target = Path.Combine(ActionDirectory, target);
|
||||
@@ -124,6 +123,7 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
Data.NodeVersion = "node20";
|
||||
}
|
||||
var nodeRuntimeVersion = await StepHost.DetermineNodeRuntimeVersion(ExecutionContext, Data.NodeVersion);
|
||||
ExecutionContext.StepTelemetry.Type = nodeRuntimeVersion;
|
||||
string file = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), nodeRuntimeVersion, "bin", $"node{IOUtil.ExeExtension}");
|
||||
|
||||
// Format the arguments passed to node.
|
||||
|
||||
@@ -127,6 +127,10 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
|
||||
// Check OS warning
|
||||
var osWarningChecker = HostContext.GetService<IOSWarningChecker>();
|
||||
await osWarningChecker.CheckOSAsync(context);
|
||||
|
||||
try
|
||||
{
|
||||
var tokenPermissions = jobContext.Global.Variables.Get("system.github.token.permissions") ?? "";
|
||||
@@ -399,7 +403,7 @@ namespace GitHub.Runner.Worker
|
||||
var snapshotOperationProvider = HostContext.GetService<ISnapshotOperationProvider>();
|
||||
jobContext.RegisterPostJobStep(new JobExtensionRunner(
|
||||
runAsync: (executionContext, _) => snapshotOperationProvider.CreateSnapshotRequestAsync(executionContext, snapshotRequest),
|
||||
condition: $"{PipelineTemplateConstants.Success}()",
|
||||
condition: snapshotRequest.Condition,
|
||||
displayName: $"Create custom image",
|
||||
data: null));
|
||||
}
|
||||
|
||||
110
src/Runner.Worker/OSWarningChecker.cs
Normal file
110
src/Runner.Worker/OSWarningChecker.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Sdk;
|
||||
|
||||
namespace GitHub.Runner.Worker
|
||||
{
|
||||
[ServiceLocator(Default = typeof(OSWarningChecker))]
|
||||
public interface IOSWarningChecker : IRunnerService
|
||||
{
|
||||
Task CheckOSAsync(IExecutionContext context);
|
||||
}
|
||||
|
||||
public sealed class OSWarningChecker : RunnerService, IOSWarningChecker
|
||||
{
|
||||
private static TimeSpan s_regexTimeout = TimeSpan.FromSeconds(1);
|
||||
|
||||
public async Task CheckOSAsync(IExecutionContext context)
|
||||
{
|
||||
ArgUtil.NotNull(context, nameof(context));
|
||||
if (!context.Global.Variables.System_TestDotNet8Compatibility)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
context.Output("Testing runner upgrade compatibility");
|
||||
List<string> output = new();
|
||||
object outputLock = new();
|
||||
try
|
||||
{
|
||||
using (var process = HostContext.CreateService<IProcessInvoker>())
|
||||
{
|
||||
process.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(stdout.Data))
|
||||
{
|
||||
lock (outputLock)
|
||||
{
|
||||
output.Add(stdout.Data);
|
||||
Trace.Info(stdout.Data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
process.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stderr)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(stderr.Data))
|
||||
{
|
||||
lock (outputLock)
|
||||
{
|
||||
output.Add(stderr.Data);
|
||||
Trace.Error(stderr.Data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using (var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(10)))
|
||||
{
|
||||
int exitCode = await process.ExecuteAsync(
|
||||
workingDirectory: HostContext.GetDirectory(WellKnownDirectory.Root),
|
||||
fileName: Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "testDotNet8Compatibility", $"TestDotNet8Compatibility{IOUtil.ExeExtension}"),
|
||||
arguments: string.Empty,
|
||||
environment: null,
|
||||
cancellationToken: cancellationTokenSource.Token);
|
||||
|
||||
var outputStr = string.Join("\n", output).Trim();
|
||||
if (exitCode != 0 || !string.Equals(outputStr, "Hello from .NET 8!", StringComparison.Ordinal))
|
||||
{
|
||||
var pattern = context.Global.Variables.System_DotNet8CompatibilityOutputPattern;
|
||||
if (!string.IsNullOrEmpty(pattern))
|
||||
{
|
||||
var regex = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant, s_regexTimeout);
|
||||
if (!regex.IsMatch(outputStr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var warningMessage = context.Global.Variables.System_DotNet8CompatibilityWarning;
|
||||
if (!string.IsNullOrEmpty(warningMessage))
|
||||
{
|
||||
context.Warning(warningMessage);
|
||||
}
|
||||
|
||||
context.Global.JobTelemetry.Add(new JobTelemetry() { Type = JobTelemetryType.General, Message = $".NET 8 OS compatibility test failed with exit code '{exitCode}' and output: {GetShortOutput(context, output)}" });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error("An error occurred while testing .NET 8 compatibility'");
|
||||
Trace.Error(ex);
|
||||
context.Global.JobTelemetry.Add(new JobTelemetry() { Type = JobTelemetryType.General, Message = $".NET 8 OS compatibility test encountered exception type '{ex.GetType().FullName}', message: '{ex.Message}', process output: '{GetShortOutput(context, output)}'" });
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetShortOutput(IExecutionContext context, List<string> output)
|
||||
{
|
||||
var length = context.Global.Variables.System_DotNet8CompatibilityOutputLength ?? 200;
|
||||
var outputStr = string.Join("\n", output).Trim();
|
||||
return outputStr.Length > length ? string.Concat(outputStr.Substring(0, length), "[...]") : outputStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,8 +72,16 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
public bool? Step_Debug => GetBoolean(Constants.Variables.Actions.StepDebug);
|
||||
|
||||
public string System_DotNet8CompatibilityWarning => Get(Constants.Variables.System.DotNet8CompatibilityWarning);
|
||||
|
||||
public string System_DotNet8CompatibilityOutputPattern => Get(Constants.Variables.System.DotNet8CompatibilityOutputPattern);
|
||||
|
||||
public int? System_DotNet8CompatibilityOutputLength => GetInt(Constants.Variables.System.DotNet8CompatibilityOutputLength);
|
||||
|
||||
public string System_PhaseDisplayName => Get(Constants.Variables.System.PhaseDisplayName);
|
||||
|
||||
public bool System_TestDotNet8Compatibility => GetBoolean(Constants.Variables.System.TestDotNet8Compatibility) ?? false;
|
||||
|
||||
public string Get(string name)
|
||||
{
|
||||
Variable variable;
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
||||
public const String If = "if";
|
||||
public const String Image = "image";
|
||||
public const String ImageName = "image-name";
|
||||
public const String CustomImageVersion = "version";
|
||||
public const String Include = "include";
|
||||
public const String Inputs = "inputs";
|
||||
public const String Job = "job";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using GitHub.DistributedTask.Expressions2;
|
||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||
@@ -349,6 +350,10 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
||||
internal static Snapshot ConvertToJobSnapshotRequest(TemplateContext context, TemplateToken token)
|
||||
{
|
||||
string imageName = null;
|
||||
string version = "1.*";
|
||||
string versionString = string.Empty;
|
||||
var condition = $"{PipelineTemplateConstants.Success}()";
|
||||
|
||||
if (token is StringToken snapshotStringLiteral)
|
||||
{
|
||||
imageName = snapshotStringLiteral.Value;
|
||||
@@ -359,11 +364,19 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
||||
foreach (var snapshotPropertyPair in snapshotMapping)
|
||||
{
|
||||
var propertyName = snapshotPropertyPair.Key.AssertString($"{PipelineTemplateConstants.Snapshot} key");
|
||||
var propertyValue = snapshotPropertyPair.Value;
|
||||
switch (propertyName.Value)
|
||||
{
|
||||
case PipelineTemplateConstants.ImageName:
|
||||
imageName = snapshotPropertyPair.Value.AssertString($"{PipelineTemplateConstants.Snapshot} {propertyName}").Value;
|
||||
break;
|
||||
case PipelineTemplateConstants.If:
|
||||
condition = ConvertToIfCondition(context, propertyValue, false);
|
||||
break;
|
||||
case PipelineTemplateConstants.CustomImageVersion:
|
||||
versionString = propertyValue.AssertString($"job {PipelineTemplateConstants.Snapshot} {PipelineTemplateConstants.CustomImageVersion}").Value;
|
||||
version = IsSnapshotImageVersionValid(versionString) ? versionString : null;
|
||||
break;
|
||||
default:
|
||||
propertyName.AssertUnexpectedValue($"{PipelineTemplateConstants.Snapshot} key");
|
||||
break;
|
||||
@@ -376,7 +389,26 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Snapshot(imageName);
|
||||
return new Snapshot(imageName)
|
||||
{
|
||||
Condition = condition,
|
||||
Version = version
|
||||
};
|
||||
}
|
||||
|
||||
private static bool IsSnapshotImageVersionValid(string versionString)
|
||||
{
|
||||
var versionSegments = versionString.Split(".");
|
||||
|
||||
if (versionSegments.Length != 2 ||
|
||||
!versionSegments[1].Equals("*") ||
|
||||
!Int32.TryParse(versionSegments[0], NumberStyles.None, CultureInfo.InvariantCulture, result: out int parsedMajor) ||
|
||||
parsedMajor < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static ActionStep ConvertToStep(
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
||||
|
||||
namespace GitHub.DistributedTask.Pipelines
|
||||
{
|
||||
[DataContract]
|
||||
public class Snapshot
|
||||
{
|
||||
public Snapshot(string imageName)
|
||||
public Snapshot(string imageName, string condition = null, string version = null)
|
||||
{
|
||||
ImageName = imageName;
|
||||
Condition = condition ?? $"{PipelineTemplateConstants.Success}()";
|
||||
Version = version ?? "1.*";
|
||||
}
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String ImageName { get; set; }
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String Condition { get; set; }
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String Version { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,11 +169,28 @@
|
||||
"image-name": {
|
||||
"type": "non-empty-string",
|
||||
"required": true
|
||||
},
|
||||
"if": "snapshot-if",
|
||||
"version": {
|
||||
"type": "non-empty-string",
|
||||
"required": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"snapshot-if": {
|
||||
"context": [
|
||||
"github",
|
||||
"inputs",
|
||||
"vars",
|
||||
"needs",
|
||||
"strategy",
|
||||
"matrix"
|
||||
],
|
||||
"string": {}
|
||||
},
|
||||
|
||||
"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
|
||||
|
||||
@@ -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,
|
||||
|
||||
20
src/Sdk/RSWebApi/Contracts/BrokerError.cs
Normal file
20
src/Sdk/RSWebApi/Contracts/BrokerError.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace GitHub.Actions.RunService.WebApi
|
||||
{
|
||||
[DataContract]
|
||||
public class BrokerError
|
||||
{
|
||||
[DataMember(Name = "source", EmitDefaultValue = false)]
|
||||
public string Source { get; set; }
|
||||
|
||||
[DataMember(Name = "errorKind", EmitDefaultValue = false)]
|
||||
public string ErrorKind { get; set; }
|
||||
|
||||
[DataMember(Name = "statusCode", EmitDefaultValue = false)]
|
||||
public int StatusCode { get; set; }
|
||||
|
||||
[DataMember(Name = "errorMessage", EmitDefaultValue = false)]
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
||||
10
src/Sdk/RSWebApi/Contracts/BrokerErrorKind.cs
Normal file
10
src/Sdk/RSWebApi/Contracts/BrokerErrorKind.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace GitHub.Actions.RunService.WebApi
|
||||
{
|
||||
[DataContract]
|
||||
public class BrokerErrorKind
|
||||
{
|
||||
public const string RunnerVersionTooOld = "RunnerVersionTooOld";
|
||||
}
|
||||
}
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,12 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
[DataMember(Name = "name", EmitDefaultValue = false)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[DataMember(Name = "ref", EmitDefaultValue = false)]
|
||||
public string Ref { get; set; }
|
||||
|
||||
[DataMember(Name = "type", EmitDefaultValue = false)]
|
||||
public string Type { get; set; }
|
||||
|
||||
[DataMember(Name = "status")]
|
||||
public TimelineRecordState? Status { 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)
|
||||
@@ -76,6 +86,7 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
httpMethod,
|
||||
requestUri: requestUri,
|
||||
content: requestContent,
|
||||
readErrorBody: true,
|
||||
cancellationToken: cancellationToken);
|
||||
|
||||
if (result.IsSuccess)
|
||||
@@ -83,14 +94,26 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
return result.Value;
|
||||
}
|
||||
|
||||
switch (result.StatusCode)
|
||||
if (TryParseErrorBody(result.ErrorBody, out RunServiceError error))
|
||||
{
|
||||
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}");
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +121,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,
|
||||
@@ -110,7 +133,7 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
{
|
||||
PlanID = planId,
|
||||
JobID = jobId,
|
||||
Conclusion = result,
|
||||
Conclusion = conclusion,
|
||||
Outputs = outputs,
|
||||
StepResults = stepResults,
|
||||
Annotations = jobAnnotations,
|
||||
@@ -120,22 +143,32 @@ 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))
|
||||
{
|
||||
case HttpStatusCode.NotFound:
|
||||
throw new TaskOrchestrationJobNotFoundException($"Job not found: {jobId}");
|
||||
default:
|
||||
throw new Exception($"Failed to complete job: {response.ReasonPhrase}");
|
||||
switch ((HttpStatusCode)error.Code)
|
||||
{
|
||||
case HttpStatusCode.NotFound:
|
||||
throw new TaskOrchestrationJobNotFoundException($"Job not found: {jobId}. {error.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,6 +192,7 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
httpMethod,
|
||||
requestUri,
|
||||
content: requestContent,
|
||||
readErrorBody: true,
|
||||
cancellationToken: cancellationToken);
|
||||
|
||||
if (result.IsSuccess)
|
||||
@@ -166,13 +200,60 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
return result.Value;
|
||||
}
|
||||
|
||||
switch (result.StatusCode)
|
||||
if (TryParseErrorBody(result.ErrorBody, out RunServiceError error))
|
||||
{
|
||||
case HttpStatusCode.NotFound:
|
||||
throw new TaskOrchestrationJobNotFoundException($"Job not found: {jobId}");
|
||||
default:
|
||||
throw new Exception($"Failed to renew job: {result.Error}");
|
||||
switch ((HttpStatusCode)error.Code)
|
||||
{
|
||||
case HttpStatusCode.NotFound:
|
||||
throw new TaskOrchestrationJobNotFoundException($"Job not found: {jobId}. {error.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,7 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
new HttpMethod("GET"),
|
||||
requestUri: requestUri,
|
||||
queryParameters: queryParams,
|
||||
readErrorBody: true,
|
||||
cancellationToken: cancellationToken);
|
||||
|
||||
if (result.IsSuccess)
|
||||
@@ -110,8 +111,21 @@ 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 (TryParseErrorBody(result.ErrorBody, out BrokerError brokerError))
|
||||
{
|
||||
switch (brokerError.ErrorKind)
|
||||
{
|
||||
case BrokerErrorKind.RunnerVersionTooOld:
|
||||
throw new AccessDeniedException(brokerError.Message)
|
||||
{
|
||||
ErrorCode = 1
|
||||
};
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// temporary back compat
|
||||
if (result.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
throw new AccessDeniedException($"{result.Error} Runner version v{runnerVersion} is deprecated and cannot receive messages.")
|
||||
@@ -120,7 +134,7 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
};
|
||||
}
|
||||
|
||||
throw new Exception($"Failed to get job message: {result.Error}");
|
||||
throw new Exception($"Failed to get job message. Request to {requestUri} failed with status: {result.StatusCode}. Error message {result.Error}");
|
||||
}
|
||||
|
||||
public async Task<TaskAgentSession> CreateSessionAsync(
|
||||
@@ -172,5 +186,26 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
|
||||
throw new Exception($"Failed to delete broker session: {result.Error}");
|
||||
}
|
||||
|
||||
private static bool TryParseErrorBody(string errorBody, out BrokerError error)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(errorBody))
|
||||
{
|
||||
try
|
||||
{
|
||||
error = JsonUtility.FromString<BrokerError>(errorBody);
|
||||
if (error?.Source == "actions-broker-listener")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
error = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}'");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
{
|
||||
@@ -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);
|
||||
|
||||
@@ -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" });
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -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
|
||||
{
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -360,5 +370,25 @@ namespace GitHub.Runner.Common.Tests
|
||||
Unloading(this, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadDefaultUserAgents()
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public class DelayEventArgs : EventArgs
|
||||
{
|
||||
public DelayEventArgs(
|
||||
TimeSpan delay,
|
||||
CancellationToken token)
|
||||
{
|
||||
Delay = delay;
|
||||
Token = token;
|
||||
}
|
||||
|
||||
public TimeSpan Delay { get; }
|
||||
|
||||
public CancellationToken Token { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,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
|
||||
{
|
||||
@@ -915,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
|
||||
{
|
||||
@@ -1051,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
|
||||
@@ -1245,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
|
||||
{
|
||||
|
||||
@@ -773,6 +773,82 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void PublishStepResult_EmbeddedStep()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
// Job request
|
||||
TaskOrchestrationPlanReference plan = new();
|
||||
TimelineReference timeline = new();
|
||||
Guid jobId = Guid.NewGuid();
|
||||
string jobName = "some job name";
|
||||
var variables = new Dictionary<string, VariableValue>()
|
||||
{
|
||||
["RunService.FixEmbeddedIssues"] = new VariableValue("true"),
|
||||
};
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, variables, 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,
|
||||
Id = "github",
|
||||
Version = "sha1"
|
||||
});
|
||||
jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
|
||||
|
||||
// Mocks
|
||||
var pagingLogger = new Mock<IPagingLogger>();
|
||||
var pagingLogger2 = new Mock<IPagingLogger>();
|
||||
var jobServerQueue = new Mock<IJobServerQueue>();
|
||||
jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.IsAny<TimelineRecord>()));
|
||||
hc.EnqueueInstance(pagingLogger.Object);
|
||||
hc.EnqueueInstance(pagingLogger2.Object);
|
||||
hc.SetSingleton(jobServerQueue.Object);
|
||||
|
||||
// Job context
|
||||
var jobContext = new Runner.Worker.ExecutionContext();
|
||||
jobContext.Initialize(hc);
|
||||
jobContext.InitializeJob(jobRequest, CancellationToken.None);
|
||||
jobContext.Start();
|
||||
|
||||
// Step 1 context
|
||||
var step1 = jobContext.CreateChild(Guid.NewGuid(), "my_step", "my_step", null, null, ActionRunStage.Main);
|
||||
step1.Start();
|
||||
|
||||
// Embedded step 1a context
|
||||
var embeddedStep1a = step1.CreateEmbeddedChild(null, null, Guid.NewGuid(), ActionRunStage.Main);
|
||||
embeddedStep1a.Start();
|
||||
embeddedStep1a.StepTelemetry.Type = "node16";
|
||||
embeddedStep1a.StepTelemetry.Action = "actions/checkout";
|
||||
embeddedStep1a.StepTelemetry.Ref = "v2";
|
||||
embeddedStep1a.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
||||
embeddedStep1a.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
||||
embeddedStep1a.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" }, ExecutionContextLogOptions.Default);
|
||||
embeddedStep1a.Complete();
|
||||
|
||||
// Embedded step 1b context
|
||||
var embeddedStep1b = step1.CreateEmbeddedChild(null, null, Guid.NewGuid(), ActionRunStage.Main);
|
||||
embeddedStep1b.Start();
|
||||
embeddedStep1b.StepTelemetry.Type = "node16";
|
||||
embeddedStep1b.StepTelemetry.Action = "actions/checkout";
|
||||
embeddedStep1b.StepTelemetry.Ref = "v2";
|
||||
embeddedStep1b.AddIssue(new Issue() { Type = IssueType.Error, Message = "error 2" }, ExecutionContextLogOptions.Default);
|
||||
embeddedStep1b.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning 2" }, ExecutionContextLogOptions.Default);
|
||||
embeddedStep1b.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice 2" }, ExecutionContextLogOptions.Default);
|
||||
embeddedStep1b.Complete();
|
||||
|
||||
step1.Complete();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(3, jobContext.Global.StepsResult.Count);
|
||||
Assert.Equal(0, jobContext.Global.StepsResult[0].Annotations.Count);
|
||||
Assert.Equal(0, jobContext.Global.StepsResult[1].Annotations.Count);
|
||||
Assert.Equal(6, jobContext.Global.StepsResult[2].Annotations.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void PublishStepResult_EmbeddedStep_Legacy()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
@@ -807,7 +883,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
ec.InitializeJob(jobRequest, CancellationToken.None);
|
||||
ec.Start();
|
||||
|
||||
var embeddedStep = ec.CreateChild(Guid.NewGuid(), "action_1_pre", "action_1_pre", null, null, ActionRunStage.Main, isEmbedded: true);
|
||||
var embeddedStep = ec.CreateEmbeddedChild(null, null, Guid.NewGuid(), ActionRunStage.Main);
|
||||
embeddedStep.Start();
|
||||
|
||||
embeddedStep.StepTelemetry.Type = "node16";
|
||||
|
||||
@@ -140,6 +140,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
hc.SetSingleton(_diagnosticLogManager.Object);
|
||||
hc.SetSingleton(_jobHookProvider.Object);
|
||||
hc.SetSingleton(_snapshotOperationProvider.Object);
|
||||
hc.SetSingleton(new Mock<IOSWarningChecker>().Object);
|
||||
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // JobExecutionContext
|
||||
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // job start hook
|
||||
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // Initial Job
|
||||
@@ -505,7 +506,27 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
return EnsureSnapshotPostJobStepForToken(mappingToken, snapshot);
|
||||
}
|
||||
|
||||
private async Task EnsureSnapshotPostJobStepForToken(TemplateToken snapshotToken, Pipelines.Snapshot expectedSnapshot)
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public Task EnsureSnapshotPostJobStepForMappingToken_WithIf_Is_False()
|
||||
{
|
||||
var snapshot = new Pipelines.Snapshot("TestImageNameFromMappingToken", condition: $"{PipelineTemplateConstants.Success}() && 1==0", version: "2.*");
|
||||
var imageNameValueStringToken = new StringToken(null, null, null, snapshot.ImageName);
|
||||
var condition = new StringToken(null, null, null, snapshot.Condition);
|
||||
var version = new StringToken(null, null, null, snapshot.Version);
|
||||
|
||||
var mappingToken = new MappingToken(null, null, null)
|
||||
{
|
||||
{ new StringToken(null,null,null, PipelineTemplateConstants.ImageName), imageNameValueStringToken },
|
||||
{ new StringToken(null,null,null, PipelineTemplateConstants.If), condition },
|
||||
{ new StringToken(null,null,null, PipelineTemplateConstants.CustomImageVersion), version }
|
||||
};
|
||||
|
||||
return EnsureSnapshotPostJobStepForToken(mappingToken, snapshot, skipSnapshotStep: true);
|
||||
}
|
||||
|
||||
private async Task EnsureSnapshotPostJobStepForToken(TemplateToken snapshotToken, Pipelines.Snapshot expectedSnapshot, bool skipSnapshotStep = false)
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
@@ -523,14 +544,28 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
|
||||
Assert.Equal(1, postJobSteps.Count);
|
||||
var snapshotStep = postJobSteps.First();
|
||||
_jobEc.JobSteps.Enqueue(snapshotStep);
|
||||
|
||||
var _stepsRunner = new StepsRunner();
|
||||
_stepsRunner.Initialize(hc);
|
||||
await _stepsRunner.RunAsync(_jobEc);
|
||||
|
||||
Assert.Equal("Create custom image", snapshotStep.DisplayName);
|
||||
Assert.Equal($"{PipelineTemplateConstants.Success}()", snapshotStep.Condition);
|
||||
Assert.Equal(expectedSnapshot.Condition ?? $"{PipelineTemplateConstants.Success}()", snapshotStep.Condition);
|
||||
|
||||
// Run the mock snapshot step, so we can verify it was executed with the expected snapshot object.
|
||||
await snapshotStep.RunAsync();
|
||||
|
||||
Assert.NotNull(_requestedSnapshot);
|
||||
Assert.Equal(expectedSnapshot.ImageName, _requestedSnapshot.ImageName);
|
||||
// await snapshotStep.RunAsync();
|
||||
if (skipSnapshotStep)
|
||||
{
|
||||
Assert.Null(_requestedSnapshot);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.NotNull(_requestedSnapshot);
|
||||
Assert.Equal(expectedSnapshot.ImageName, _requestedSnapshot.ImageName);
|
||||
Assert.Equal(expectedSnapshot.Condition ?? $"{PipelineTemplateConstants.Success}()", _requestedSnapshot.Condition);
|
||||
Assert.Equal(expectedSnapshot.Version ?? "1.*", _requestedSnapshot.Version);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit" Version="2.7.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||
<PackageReference Include="System.Buffers" Version="4.5.1" />
|
||||
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.4.0" />
|
||||
|
||||
13
src/TestDotNet8Compatibility/Program.cs
Normal file
13
src/TestDotNet8Compatibility/Program.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace TestDotNet8Compatibility
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("Hello from .NET 8!");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/TestDotNet8Compatibility/TestDotNet8Compatibility.csproj
Normal file
18
src/TestDotNet8Compatibility/TestDotNet8Compatibility.csproj
Normal file
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<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>
|
||||
<Version>$(Version)</Version>
|
||||
<PredefinedCulturesOnly>false</PredefinedCulturesOnly>
|
||||
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DebugType>portable</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
22
src/TestDotNet8Compatibility/dir.proj
Normal file
22
src/TestDotNet8Compatibility/dir.proj
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build"
|
||||
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectFiles Include="TestDotNet8Compatibility.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="Build">
|
||||
<MSBuild Targets="Restore" Projects="@(ProjectFiles)" StopOnFirstFailure="true" />
|
||||
<MSBuild Targets="Publish" Projects="@(ProjectFiles)" BuildInParallel="false" StopOnFirstFailure="true" Properties="Configuration=$(BUILDCONFIG);PackageRuntime=$(PackageRuntime);Version=$(RunnerVersion);RuntimeIdentifier=$(PackageRuntime);PublishDir=$(MSBuildProjectDirectory)/../../_layout/bin/testDotNet8Compatibility" />
|
||||
</Target>
|
||||
|
||||
<Target Name="Clean">
|
||||
<RemoveDir Directories="$(MSBuildProjectDirectory)/../../_layout/bin/testDotNet8Compatibility" />
|
||||
<RemoveDir Directories="TestDotNet8Compatibility/bin" />
|
||||
<RemoveDir Directories="TestDotNet8Compatibility/obj" />
|
||||
</Target>
|
||||
|
||||
<Target Name="Layout" DependsOnTargets="Clean;Build">
|
||||
</Target>
|
||||
</Project>
|
||||
5
src/TestDotNet8Compatibility/global.json
Normal file
5
src/TestDotNet8Compatibility/global.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "8.0.303"
|
||||
}
|
||||
}
|
||||
56
src/dev.sh
56
src/dev.sh
@@ -17,8 +17,10 @@ LAYOUT_DIR="$SCRIPT_DIR/../_layout"
|
||||
DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
|
||||
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
||||
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
||||
DOTNETSDK_VERSION="6.0.421"
|
||||
DOTNETSDK_VERSION="6.0.425"
|
||||
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
||||
DOTNET8SDK_VERSION="8.0.303"
|
||||
DOTNET8SDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNET8SDK_VERSION"
|
||||
RUNNER_VERSION=$(cat runnerversion)
|
||||
|
||||
pushd "$SCRIPT_DIR"
|
||||
@@ -125,6 +127,19 @@ function build ()
|
||||
{
|
||||
heading "Building ..."
|
||||
dotnet msbuild -t:Build -p:PackageRuntime="${RUNTIME_ID}" -p:BUILDCONFIG="${BUILD_CONFIG}" -p:RunnerVersion="${RUNNER_VERSION}" ./dir.proj || failed build
|
||||
|
||||
# Build TestDotNet8Compatibility
|
||||
heading "Building .NET 8 compatibility test"
|
||||
echo "Prepend ${DOTNET8SDK_INSTALLDIR} to %PATH%" # Prepend .NET 8 SDK to PATH
|
||||
PATH_BAK=$PATH
|
||||
export PATH=${DOTNET8SDK_INSTALLDIR}:$PATH
|
||||
pushd "$SCRIPT_DIR/TestDotNet8Compatibility" > /dev/null # Working directory
|
||||
pwd
|
||||
echo "Dotnet 8 SDK Version"
|
||||
dotnet --version
|
||||
dotnet msbuild -t:Build -p:PackageRuntime="${RUNTIME_ID}" -p:BUILDCONFIG="${BUILD_CONFIG}" -p:RunnerVersion="${RUNNER_VERSION}" ./dir.proj || failed build
|
||||
popd > /dev/null # Restore working directory
|
||||
export PATH=$PATH_BAK # Restore PATH
|
||||
}
|
||||
|
||||
function layout ()
|
||||
@@ -143,6 +158,18 @@ function layout ()
|
||||
|
||||
heading "Setup externals folder for $RUNTIME_ID runner's layout"
|
||||
bash ./Misc/externals.sh $RUNTIME_ID || checkRC externals.sh
|
||||
|
||||
# Build TestDotNet8Compatibility
|
||||
echo "Prepend ${DOTNET8SDK_INSTALLDIR} to %PATH%" # Prepend .NET 8 SDK to PATH
|
||||
PATH_BAK=$PATH
|
||||
export PATH=${DOTNET8SDK_INSTALLDIR}:$PATH
|
||||
pushd "$SCRIPT_DIR/TestDotNet8Compatibility" > /dev/null # Working directory
|
||||
heading "Dotnet 8 SDK Version"
|
||||
dotnet --version
|
||||
heading "Building .NET 8 compatibility test"
|
||||
dotnet msbuild -t:layout -p:PackageRuntime="${RUNTIME_ID}" -p:BUILDCONFIG="${BUILD_CONFIG}" -p:RunnerVersion="${RUNNER_VERSION}" ./dir.proj || failed build
|
||||
popd > /dev/null # Restore working directory
|
||||
export PATH=$PATH_BAK # Restore PATH
|
||||
}
|
||||
|
||||
function runtest ()
|
||||
@@ -199,6 +226,7 @@ function package ()
|
||||
popd > /dev/null
|
||||
}
|
||||
|
||||
# Install .NET SDK
|
||||
if [[ (! -d "${DOTNETSDK_INSTALLDIR}") || (! -e "${DOTNETSDK_INSTALLDIR}/.${DOTNETSDK_VERSION}") || (! -e "${DOTNETSDK_INSTALLDIR}/dotnet") ]]; then
|
||||
|
||||
# Download dotnet SDK to ../_dotnetsdk directory
|
||||
@@ -224,6 +252,32 @@ if [[ (! -d "${DOTNETSDK_INSTALLDIR}") || (! -e "${DOTNETSDK_INSTALLDIR}/.${DOTN
|
||||
echo "${DOTNETSDK_VERSION}" > "${DOTNETSDK_INSTALLDIR}/.${DOTNETSDK_VERSION}"
|
||||
fi
|
||||
|
||||
# Install .NET 8 SDK
|
||||
if [[ (! -d "${DOTNET8SDK_INSTALLDIR}") || (! -e "${DOTNET8SDK_INSTALLDIR}/.${DOTNET8SDK_VERSION}") || (! -e "${DOTNET8SDK_INSTALLDIR}/dotnet") ]]; then
|
||||
|
||||
# Download dotnet 8 SDK to ../_dotnetsdk directory
|
||||
heading "Ensure Dotnet 8 SDK"
|
||||
|
||||
# _dotnetsdk
|
||||
# \1.0.x
|
||||
# \dotnet
|
||||
# \.1.0.x
|
||||
echo "Download dotnet8sdk into ${DOTNET8SDK_INSTALLDIR}"
|
||||
rm -Rf "${DOTNETSDK_DIR}"
|
||||
|
||||
# run dotnet-install.ps1 on windows, dotnet-install.sh on linux
|
||||
if [[ ("$CURRENT_PLATFORM" == "windows") ]]; then
|
||||
echo "Convert ${DOTNET8SDK_INSTALLDIR} to Windows style path"
|
||||
sdkinstallwindow_path=${DOTNET8SDK_INSTALLDIR:1}
|
||||
sdkinstallwindow_path=${sdkinstallwindow_path:0:1}:${sdkinstallwindow_path:1}
|
||||
$POWERSHELL -NoLogo -Sta -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command "& \"./Misc/dotnet-install.ps1\" -Version ${DOTNET8SDK_VERSION} -InstallDir \"${sdkinstallwindow_path}\" -NoPath; exit \$LastExitCode;" || checkRC dotnet-install.ps1
|
||||
else
|
||||
bash ./Misc/dotnet-install.sh --version ${DOTNET8SDK_VERSION} --install-dir "${DOTNET8SDK_INSTALLDIR}" --no-path || checkRC dotnet-install.sh
|
||||
fi
|
||||
|
||||
echo "${DOTNET8SDK_VERSION}" > "${DOTNET8SDK_INSTALLDIR}/.${DOTNET8SDK_VERSION}"
|
||||
fi
|
||||
|
||||
echo "Prepend ${DOTNETSDK_INSTALLDIR} to %PATH%"
|
||||
export PATH=${DOTNETSDK_INSTALLDIR}:$PATH
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "6.0.421"
|
||||
"version": "6.0.425"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.316.0
|
||||
2.319.0
|
||||
|
||||
Reference in New Issue
Block a user