mirror of
https://github.com/actions/runner.git
synced 2025-12-10 20:36:49 +00:00
Compare commits
5 Commits
fhammerl/s
...
pje/broker
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40f813e0fb | ||
|
|
31436b3c38 | ||
|
|
4ba8bcd9ab | ||
|
|
9c81a7d682 | ||
|
|
75a11dac1b |
@@ -4,7 +4,7 @@
|
|||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
||||||
"ghcr.io/devcontainers/features/dotnet": {
|
"ghcr.io/devcontainers/features/dotnet": {
|
||||||
"version": "6.0.415"
|
"version": "6.0.414"
|
||||||
},
|
},
|
||||||
"ghcr.io/devcontainers/features/node:1": {
|
"ghcr.io/devcontainers/features/node:1": {
|
||||||
"version": "16"
|
"version": "16"
|
||||||
@@ -21,4 +21,4 @@
|
|||||||
},
|
},
|
||||||
"postCreateCommand": "dotnet restore src/Test && dotnet restore src/Runner.PluginHost",
|
"postCreateCommand": "dotnet restore src/Test && dotnet restore src/Runner.PluginHost",
|
||||||
"remoteUser": "vscode"
|
"remoteUser": "vscode"
|
||||||
}
|
}
|
||||||
18
.github/workflows/close-bugs-bot.yml
vendored
18
.github/workflows/close-bugs-bot.yml
vendored
@@ -1,18 +0,0 @@
|
|||||||
name: Close Bugs Bot
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * *' # every day at midnight
|
|
||||||
jobs:
|
|
||||||
stale:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/stale@v8
|
|
||||||
with:
|
|
||||||
close-issue-message: "This issue does not seem to be a problem with the runner application, it concerns the GitHub actions platform more generally. Could you please post your feedback on the [GitHub Community Support Forum](https://github.com/orgs/community/discussions/categories/actions) which is actively monitored. Using the forum ensures that we route your problem to the correct team. 😃"
|
|
||||||
exempt-issue-labels: "keep"
|
|
||||||
stale-issue-label: "actions-bug"
|
|
||||||
only-labels: "actions-bug"
|
|
||||||
days-before-stale: 0
|
|
||||||
days-before-close: 1
|
|
||||||
close-issue-reason: "completed"
|
|
||||||
18
.github/workflows/close-features-bot.yml
vendored
18
.github/workflows/close-features-bot.yml
vendored
@@ -1,18 +0,0 @@
|
|||||||
name: Close Features Bot
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * *' # every day at midnight
|
|
||||||
jobs:
|
|
||||||
stale:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/stale@v8
|
|
||||||
with:
|
|
||||||
close-issue-message: "Thank you for your interest in the runner application and taking the time to provide your valuable feedback. We kindly ask you to redirect this feedback to the [GitHub Community Support Forum](https://github.com/orgs/community/discussions/categories/actions-and-packages) which our team actively monitors and would be a better place to start a discussion for new feature requests in GitHub Actions. For more information on this policy please [read our contribution guidelines](https://github.com/actions/runner#contribute). 😃"
|
|
||||||
exempt-issue-labels: "keep"
|
|
||||||
stale-issue-label: "actions-feature"
|
|
||||||
only-labels: "actions-feature"
|
|
||||||
days-before-stale: 0
|
|
||||||
days-before-close: 1
|
|
||||||
close-issue-reason: "completed"
|
|
||||||
@@ -5,8 +5,7 @@ ARG TARGETOS
|
|||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG RUNNER_VERSION
|
ARG RUNNER_VERSION
|
||||||
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.4.0
|
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.4.0
|
||||||
ARG DOCKER_VERSION=24.0.6
|
ARG DOCKER_VERSION=23.0.6
|
||||||
ARG BUILDX_VERSION=0.11.2
|
|
||||||
|
|
||||||
RUN apt update -y && apt install curl unzip -y
|
RUN apt update -y && apt install curl unzip -y
|
||||||
|
|
||||||
@@ -26,11 +25,7 @@ RUN export RUNNER_ARCH=${TARGETARCH} \
|
|||||||
&& if [ "$RUNNER_ARCH" = "arm64" ]; then export DOCKER_ARCH=aarch64 ; fi \
|
&& if [ "$RUNNER_ARCH" = "arm64" ]; then export DOCKER_ARCH=aarch64 ; fi \
|
||||||
&& curl -fLo docker.tgz https://download.docker.com/${TARGETOS}/static/stable/${DOCKER_ARCH}/docker-${DOCKER_VERSION}.tgz \
|
&& curl -fLo docker.tgz https://download.docker.com/${TARGETOS}/static/stable/${DOCKER_ARCH}/docker-${DOCKER_VERSION}.tgz \
|
||||||
&& tar zxvf docker.tgz \
|
&& tar zxvf docker.tgz \
|
||||||
&& rm -rf docker.tgz \
|
&& rm -rf docker.tgz
|
||||||
&& mkdir -p /usr/local/lib/docker/cli-plugins \
|
|
||||||
&& curl -fLo /usr/local/lib/docker/cli-plugins/docker-buildx \
|
|
||||||
"https://github.com/docker/buildx/releases/download/v${BUILDX_VERSION}/buildx-v${BUILDX_VERSION}.linux-${TARGETARCH}" \
|
|
||||||
&& chmod +x /usr/local/lib/docker/cli-plugins/docker-buildx
|
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/runtime-deps:6.0-jammy
|
FROM mcr.microsoft.com/dotnet/runtime-deps:6.0-jammy
|
||||||
|
|
||||||
@@ -54,7 +49,6 @@ RUN adduser --disabled-password --gecos "" --uid 1001 runner \
|
|||||||
WORKDIR /home/runner
|
WORKDIR /home/runner
|
||||||
|
|
||||||
COPY --chown=runner:docker --from=build /actions-runner .
|
COPY --chown=runner:docker --from=build /actions-runner .
|
||||||
COPY --from=build /usr/local/lib/docker/cli-plugins/docker-buildx /usr/local/lib/docker/cli-plugins/docker-buildx
|
|
||||||
|
|
||||||
RUN install -o root -g root -m 755 docker/* /usr/bin/ && rm -rf docker
|
RUN install -o root -g root -m 755 docker/* /usr/bin/ && rm -rf docker
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,37 @@
|
|||||||
## What's Changed
|
## What's Changed
|
||||||
* Trim whitespace in `./Misc/contentHash/dotnetRuntime/*` by @TingluoHuang in https://github.com/actions/runner/pull/2915
|
* Bump @types/node from 12.12.14 to 20.4.10 in /src/Misc/expressionFunc/hashFiles by @dependabot in https://github.com/actions/runner/pull/2759
|
||||||
* Send os and arch during long poll by @luketomlinson in https://github.com/actions/runner/pull/2913
|
* Trace x-github-request-id when download action tarball. by @TingluoHuang in https://github.com/actions/runner/pull/2755
|
||||||
* Revert "Update default version to node20 (#2844)" by @takost in https://github.com/actions/runner/pull/2918
|
* Fix typo by @kyanny in https://github.com/actions/runner/pull/2741
|
||||||
* Fix telemetry publish from JobServerQueue. by @TingluoHuang in https://github.com/actions/runner/pull/2919
|
* Bump prettier from 3.0.1 to 3.0.2 in /src/Misc/expressionFunc/hashFiles by @dependabot in https://github.com/actions/runner/pull/2772
|
||||||
* Use block blob instead of append blob by @yacaovsnc in https://github.com/actions/runner/pull/2924
|
* Bump @types/node from 20.4.10 to 20.5.0 in /src/Misc/expressionFunc/hashFiles by @dependabot in https://github.com/actions/runner/pull/2773
|
||||||
* Provide detail info on untar failures. by @TingluoHuang in https://github.com/actions/runner/pull/2939
|
* Revert "Fixed a bug where a misplaced `=` character could bypass here… by @cory-miller in https://github.com/actions/runner/pull/2774
|
||||||
* Bump node.js to 20.8.1 by @TingluoHuang in https://github.com/actions/runner/pull/2945
|
* Filter NODE_OPTIONS from env for file output by @cory-miller in https://github.com/actions/runner/pull/2775
|
||||||
* Update dotnet sdk to latest version @6.0.415 by @github-actions in https://github.com/actions/runner/pull/2929
|
* Bump @types/node from 20.5.0 to 20.5.1 in /src/Misc/expressionFunc/hashFiles by @dependabot in https://github.com/actions/runner/pull/2781
|
||||||
* Fix typo in log strings by @rajbos in https://github.com/actions/runner/pull/2695
|
* Update Docker Version in Images by @ajschmidt8 in https://github.com/actions/runner/pull/2694
|
||||||
* feat: add support of arm64 arch runners in service creation script by @tuxity in https://github.com/actions/runner/pull/2606
|
* Bump @types/node from 20.5.1 to 20.5.4 in /src/Misc/expressionFunc/hashFiles by @dependabot in https://github.com/actions/runner/pull/2789
|
||||||
* Add `buildx` to images by @ajschmidt8 in https://github.com/actions/runner/pull/2901
|
* Bump @typescript-eslint/parser from 6.4.0 to 6.4.1 in /src/Misc/expressionFunc/hashFiles by @dependabot in https://github.com/actions/runner/pull/2785
|
||||||
|
* Bump Microsoft.AspNet.WebApi.Client from 5.2.4 to 5.2.9 in /src by @dependabot in https://github.com/actions/runner/pull/2751
|
||||||
|
* Bump System.Buffers from 4.3.0 to 4.5.1 in /src by @dependabot in https://github.com/actions/runner/pull/2749
|
||||||
|
* Bump dotnet/runtime-deps from 6.0-jammy to 7.0-jammy in /images by @dependabot in https://github.com/actions/runner/pull/2745
|
||||||
|
* Remove need to manually compile JS binary for hashFiles utility by @vanZeben in https://github.com/actions/runner/pull/2770
|
||||||
|
* Revert "Bump dotnet/runtime-deps from 6.0-jammy to 7.0-jammy in /images" by @TingluoHuang in https://github.com/actions/runner/pull/2790
|
||||||
|
* Query runner by name on server side. by @TingluoHuang in https://github.com/actions/runner/pull/2771
|
||||||
|
* Bump typescript from 5.1.6 to 5.2.2 in /src/Misc/expressionFunc/hashFiles by @dependabot in https://github.com/actions/runner/pull/2795
|
||||||
|
* Bump @types/node from 20.5.4 to 20.5.6 in /src/Misc/expressionFunc/hashFiles by @dependabot in https://github.com/actions/runner/pull/2796
|
||||||
|
* Bump Newtonsoft.Json from 13.0.1 to 13.0.3 in /src by @dependabot in https://github.com/actions/runner/pull/2797
|
||||||
|
* Support replacing runners in v2 flow by @luketomlinson in https://github.com/actions/runner/pull/2791
|
||||||
|
* Delegating handler for Http redirects by @paveliak in https://github.com/actions/runner/pull/2814
|
||||||
|
* Add references to the firewall requirements docs by @paveliak in https://github.com/actions/runner/pull/2815
|
||||||
|
* Create automated workflow that will auto-generate dotnet sdk patches by @vanZeben in https://github.com/actions/runner/pull/2776
|
||||||
|
* Fixes minor issues with using proper output varaibles by @vanZeben in https://github.com/actions/runner/pull/2818
|
||||||
|
* Throw NonRetryableException on GetNextMessage from broker as needed. by @TingluoHuang in https://github.com/actions/runner/pull/2828
|
||||||
|
* Mark action download failures as infra failures by @cory-miller in https://github.com/actions/runner/pull/2827
|
||||||
|
|
||||||
## New Contributors
|
## New Contributors
|
||||||
* @tuxity made their first contribution in https://github.com/actions/runner/pull/2606
|
* @kyanny made their first contribution in https://github.com/actions/runner/pull/2741
|
||||||
|
* @ajschmidt8 made their first contribution in https://github.com/actions/runner/pull/2694
|
||||||
|
|
||||||
**Full Changelog**: https://github.com/actions/runner/compare/v2.310.2...v2.311.0
|
**Full Changelog**: https://github.com/actions/runner/compare/v2.308.0...v2.309.0
|
||||||
|
|
||||||
_Note: Actions Runner follows a progressive release policy, so the latest release might not be available to your enterprise, organization, or repository yet.
|
_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.
|
To confirm which version of the Actions Runner you should expect, please view the download instructions for your enterprise, organization, or repository.
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ set -e
|
|||||||
# Configures it as a service more secure
|
# Configures it as a service more secure
|
||||||
# Should be used on VMs and not containers
|
# Should be used on VMs and not containers
|
||||||
# Works on OSX and Linux
|
# Works on OSX and Linux
|
||||||
# Assumes x64 arch (support arm64)
|
# Assumes x64 arch
|
||||||
# See EXAMPLES below
|
# See EXAMPLES below
|
||||||
|
|
||||||
flags_found=false
|
flags_found=false
|
||||||
@@ -87,9 +87,6 @@ sudo echo
|
|||||||
runner_plat=linux
|
runner_plat=linux
|
||||||
[ ! -z "$(which sw_vers)" ] && runner_plat=osx;
|
[ ! -z "$(which sw_vers)" ] && runner_plat=osx;
|
||||||
|
|
||||||
runner_arch=x64
|
|
||||||
[ ! -z "$(arch | grep arm64)" ] && runner_arch=arm64
|
|
||||||
|
|
||||||
function fatal()
|
function fatal()
|
||||||
{
|
{
|
||||||
echo "error: $1" >&2
|
echo "error: $1" >&2
|
||||||
@@ -142,7 +139,7 @@ echo "Downloading latest runner ..."
|
|||||||
# For the GHES Alpha, download the runner from github.com
|
# For the GHES Alpha, download the runner from github.com
|
||||||
latest_version_label=$(curl -s -X GET 'https://api.github.com/repos/actions/runner/releases/latest' | jq -r '.tag_name')
|
latest_version_label=$(curl -s -X GET 'https://api.github.com/repos/actions/runner/releases/latest' | jq -r '.tag_name')
|
||||||
latest_version=$(echo ${latest_version_label:1})
|
latest_version=$(echo ${latest_version_label:1})
|
||||||
runner_file="actions-runner-${runner_plat}-${runner_arch}-${latest_version}.tar.gz"
|
runner_file="actions-runner-${runner_plat}-x64-${latest_version}.tar.gz"
|
||||||
|
|
||||||
if [ -f "${runner_file}" ]; then
|
if [ -f "${runner_file}" ]; then
|
||||||
echo "${runner_file} exists. skipping download."
|
echo "${runner_file} exists. skipping download."
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
531b31914e525ecb12cc5526415bc70a112ebc818f877347af1a231011f539c5
|
7539d33c35b0bc94ee67e3c0de1a6bac5ef89ce8e8efaa110131fa0520a54fb4
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
722dd5fa5ecc207fcccf67f6e502d689f2119d8117beff2041618fba17dc66a4
|
d71a31f9a17e1a41d6e1edea596edfa68a0db5948ed160e86f2154a547f4dd10
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
8ca75c76e15ab9dc7fe49a66c5c74e171e7fabd5d26546fda8931bd11bff30f9
|
3c2f700d8a995efe7895614ee07d9c7880f872d214b45983ad6163e1931870ab
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
70496eb1c99b39b3373b5088c95a35ebbaac1098e6c47c8aab94771f3ffbf501
|
b2d85c95ecad13d352f4c7d31c64dbb0d9c6381b48fa5874c4c72a43a025a8a1
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
4f8d48727d535daabcaec814e0dafb271c10625366c78e7e022ca7477a73023f
|
417d835c1a108619886b4bb5d25988cb6c138eb7b4c00320b1d9455c5630bff9
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
d54d7428f2b9200a0030365a6a4e174e30a1b29b922f8254dffb2924bd09549d
|
8f35aaecfb53426ea10816442e23065142bab9dd0fb712a29e0fc471d13c44ac
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
eaa939c45307f46b7003902255b3a2a09287215d710984107667e03ac493eb26
|
811c7debdfc54d074385b063b83c997e5360c8a9160cd20fe777713968370063
|
||||||
|
|||||||
2
src/Misc/contentHash/externals/linux-arm
vendored
2
src/Misc/contentHash/externals/linux-arm
vendored
@@ -1 +1 @@
|
|||||||
4bf3e1af0d482af1b2eaf9f08250248a8c1aea8ec20a3c5be116d58cdd930009
|
97cbac637d592d3a5d20f6cd91a3afaf5257995c7f6fdc73ab1b5a3a464e4382
|
||||||
2
src/Misc/contentHash/externals/linux-arm64
vendored
2
src/Misc/contentHash/externals/linux-arm64
vendored
@@ -1 +1 @@
|
|||||||
ec1719a8cb4d8687328aa64f4aa7c4e3498a715d8939117874782e3e6e63a14b
|
25eaf1d30e72a521414384c24b7474037698325c233503671eceaacf6a56c6bd
|
||||||
2
src/Misc/contentHash/externals/linux-x64
vendored
2
src/Misc/contentHash/externals/linux-x64
vendored
@@ -1 +1 @@
|
|||||||
50538de29f173bb73f708c4ed2c8328a62b8795829b97b2a6cb57197e2305287
|
93865f08e52e0fb0fe0119dca2363f221fbe10af5bd932a0fc3df999143a7f81
|
||||||
2
src/Misc/contentHash/externals/osx-arm64
vendored
2
src/Misc/contentHash/externals/osx-arm64
vendored
@@ -1 +1 @@
|
|||||||
a0a96cbb7593643b69e669bf14d7b29b7f27800b3a00bb3305aebe041456c701
|
2574465a73ef1de75cd01da9232a96d4b6e9a0090e368978ff48d0629137610b
|
||||||
2
src/Misc/contentHash/externals/osx-x64
vendored
2
src/Misc/contentHash/externals/osx-x64
vendored
@@ -1 +1 @@
|
|||||||
6255b22692779467047ecebd60ad46984866d75cdfe10421d593a7b51d620b09
|
ac60e452c01d99e23e696cc984f8e08b2602b649a370fc3ef1451f3958f2df0f
|
||||||
2
src/Misc/contentHash/externals/win-arm64
vendored
2
src/Misc/contentHash/externals/win-arm64
vendored
@@ -1 +1 @@
|
|||||||
6ff1abd055dc35bfbf06f75c2f08908f660346f66ad1d8f81c910068e9ba029d
|
763d18de11c11fd299c0e75e98fefc8a0e6605ae0ad6aba3bbc110db2262ab41
|
||||||
2
src/Misc/contentHash/externals/win-x64
vendored
2
src/Misc/contentHash/externals/win-x64
vendored
@@ -1 +1 @@
|
|||||||
433a6d748742d12abd20dc2a79b62ac3d9718ae47ef26f8e84dc8c180eea3659
|
c7e94c3c73ccebf214497c5ae2b6aac6eb6677c0d2080929b0a87c576c6f3858
|
||||||
@@ -6,7 +6,7 @@ NODE_URL=https://nodejs.org/dist
|
|||||||
UNOFFICIAL_NODE_URL=https://unofficial-builds.nodejs.org/download/release
|
UNOFFICIAL_NODE_URL=https://unofficial-builds.nodejs.org/download/release
|
||||||
NODE_ALPINE_URL=https://github.com/actions/alpine_nodejs/releases/download
|
NODE_ALPINE_URL=https://github.com/actions/alpine_nodejs/releases/download
|
||||||
NODE16_VERSION="16.20.2"
|
NODE16_VERSION="16.20.2"
|
||||||
NODE20_VERSION="20.8.1"
|
NODE20_VERSION="20.5.0"
|
||||||
# used only for win-arm64, remove node16 unofficial version when official version is available
|
# used only for win-arm64, remove node16 unofficial version when official version is available
|
||||||
NODE16_UNOFFICIAL_VERSION="16.20.0"
|
NODE16_UNOFFICIAL_VERSION="16.20.0"
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
SET UPDATEFILE=update.finished
|
SET UPDATEFILE=update.finished
|
||||||
"%~dp0\bin\Runner.Listener.exe" run %*
|
"%~dp0\bin\Runner.Listener.exe" run %*
|
||||||
|
|
||||||
rem using `if %ERRORLEVEL% EQU N` instead of `if ERRORLEVEL N`
|
rem using `if %ERRORLEVEL% EQU N` insterad of `if ERRORLEVEL N`
|
||||||
rem `if ERRORLEVEL N` means: error level is N or MORE
|
rem `if ERRORLEVEL N` means: error level is N or MORE
|
||||||
|
|
||||||
if %ERRORLEVEL% EQU 0 (
|
if %ERRORLEVEL% EQU 0 (
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ mscordaccore_arm64_arm64_6.0.522.21309.dll
|
|||||||
mscordaccore_amd64_amd64_6.0.1322.58009.dll
|
mscordaccore_amd64_amd64_6.0.1322.58009.dll
|
||||||
mscordaccore_amd64_amd64_6.0.2023.32017.dll
|
mscordaccore_amd64_amd64_6.0.2023.32017.dll
|
||||||
mscordaccore_amd64_amd64_6.0.2223.42425.dll
|
mscordaccore_amd64_amd64_6.0.2223.42425.dll
|
||||||
mscordaccore_amd64_amd64_6.0.2323.48002.dll
|
|
||||||
mscordbi.dll
|
mscordbi.dll
|
||||||
mscorlib.dll
|
mscorlib.dll
|
||||||
mscorrc.debug.dll
|
mscorrc.debug.dll
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
using System;
|
#nullable enable
|
||||||
using System.Collections.Generic;
|
|
||||||
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.Actions.RunService.WebApi;
|
using GitHub.Actions.RunService.WebApi;
|
||||||
using GitHub.DistributedTask.Pipelines;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
using GitHub.Services.Common;
|
using GitHub.Services.Common;
|
||||||
using Sdk.RSWebApi.Contracts;
|
|
||||||
using Sdk.WebApi.WebApi.RawClient;
|
using Sdk.WebApi.WebApi.RawClient;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common
|
namespace GitHub.Runner.Common
|
||||||
@@ -15,40 +14,35 @@ namespace GitHub.Runner.Common
|
|||||||
[ServiceLocator(Default = typeof(BrokerServer))]
|
[ServiceLocator(Default = typeof(BrokerServer))]
|
||||||
public interface IBrokerServer : IRunnerService
|
public interface IBrokerServer : IRunnerService
|
||||||
{
|
{
|
||||||
Task ConnectAsync(Uri serverUrl, VssCredentials credentials);
|
Task<BrokerSession> CreateSessionAsync(Uri serverUrl, VssCredentials credentials, CancellationToken token);
|
||||||
|
|
||||||
Task<TaskAgentMessage> GetRunnerMessageAsync(CancellationToken token, TaskAgentStatus status, string version, string os, string architecture, bool disableUpdate);
|
Task<TaskAgentMessage> GetRunnerMessageAsync(CancellationToken token, TaskAgentStatus status, string version);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class BrokerServer : RunnerService, IBrokerServer
|
public sealed class BrokerServer : RunnerService, IBrokerServer
|
||||||
{
|
{
|
||||||
private bool _hasConnection;
|
private RawConnection? _connection;
|
||||||
private Uri _brokerUri;
|
private BrokerHttpClient? _brokerHttpClient;
|
||||||
private RawConnection _connection;
|
private BrokerSession? _session;
|
||||||
private BrokerHttpClient _brokerHttpClient;
|
|
||||||
|
|
||||||
public async Task ConnectAsync(Uri serverUri, VssCredentials credentials)
|
public async Task<BrokerSession> CreateSessionAsync(Uri serverUri, VssCredentials credentials, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_brokerUri = serverUri;
|
|
||||||
|
|
||||||
_connection = VssUtil.CreateRawConnection(serverUri, credentials);
|
_connection = VssUtil.CreateRawConnection(serverUri, credentials);
|
||||||
_brokerHttpClient = await _connection.GetClientAsync<BrokerHttpClient>();
|
_brokerHttpClient = await _connection.GetClientAsync<BrokerHttpClient>(cancellationToken);
|
||||||
_hasConnection = true;
|
return await RetryRequest(
|
||||||
|
async () => _session = await _brokerHttpClient.CreateSessionAsync(),
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CheckConnection()
|
public Task<TaskAgentMessage> GetRunnerMessageAsync(CancellationToken cancellationToken, TaskAgentStatus status, string version)
|
||||||
{
|
{
|
||||||
if (!_hasConnection)
|
if (_connection is null || _session is null || _brokerHttpClient is null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"SetConnection");
|
throw new InvalidOperationException($"SetConnection");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public Task<TaskAgentMessage> GetRunnerMessageAsync(CancellationToken cancellationToken, TaskAgentStatus status, string version, string os, string architecture, bool disableUpdate)
|
|
||||||
{
|
|
||||||
CheckConnection();
|
|
||||||
var jobMessage = RetryRequest<TaskAgentMessage>(
|
var jobMessage = RetryRequest<TaskAgentMessage>(
|
||||||
async () => await _brokerHttpClient.GetRunnerMessageAsync(version, status, os, architecture, disableUpdate, cancellationToken), cancellationToken);
|
async () => await _brokerHttpClient.GetRunnerMessageAsync(_session.id, version, status, cancellationToken), cancellationToken);
|
||||||
|
|
||||||
return jobMessage;
|
return jobMessage;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -261,7 +261,6 @@ namespace GitHub.Runner.Common
|
|||||||
public static readonly string ForcedInternalNodeVersion = "ACTIONS_RUNNER_FORCED_INTERNAL_NODE_VERSION";
|
public static readonly string ForcedInternalNodeVersion = "ACTIONS_RUNNER_FORCED_INTERNAL_NODE_VERSION";
|
||||||
public static readonly string ForcedActionsNodeVersion = "ACTIONS_RUNNER_FORCE_ACTIONS_NODE_VERSION";
|
public static readonly string ForcedActionsNodeVersion = "ACTIONS_RUNNER_FORCE_ACTIONS_NODE_VERSION";
|
||||||
public static readonly string PrintLogToStdout = "ACTIONS_RUNNER_PRINT_LOG_TO_STDOUT";
|
public static readonly string PrintLogToStdout = "ACTIONS_RUNNER_PRINT_LOG_TO_STDOUT";
|
||||||
public static readonly string ActionArchiveCacheDirectory = "ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class System
|
public static class System
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -15,11 +14,10 @@ namespace GitHub.Runner.Common
|
|||||||
[ServiceLocator(Default = typeof(JobServerQueue))]
|
[ServiceLocator(Default = typeof(JobServerQueue))]
|
||||||
public interface IJobServerQueue : IRunnerService, IThrottlingReporter
|
public interface IJobServerQueue : IRunnerService, IThrottlingReporter
|
||||||
{
|
{
|
||||||
IList<JobTelemetry> JobTelemetries { get; }
|
|
||||||
TaskCompletionSource<int> JobRecordUpdated { get; }
|
TaskCompletionSource<int> JobRecordUpdated { get; }
|
||||||
event EventHandler<ThrottlingEventArgs> JobServerQueueThrottling;
|
event EventHandler<ThrottlingEventArgs> JobServerQueueThrottling;
|
||||||
Task ShutdownAsync();
|
Task ShutdownAsync();
|
||||||
void Start(Pipelines.AgentJobRequestMessage jobRequest, bool resultsServiceOnly = false, bool enableTelemetry = false);
|
void Start(Pipelines.AgentJobRequestMessage jobRequest, bool resultsServiceOnly = false);
|
||||||
void QueueWebConsoleLine(Guid stepRecordId, string line, long? lineNumber = null);
|
void QueueWebConsoleLine(Guid stepRecordId, string line, long? lineNumber = null);
|
||||||
void QueueFileUpload(Guid timelineId, Guid timelineRecordId, string type, string name, string path, bool deleteSource);
|
void QueueFileUpload(Guid timelineId, Guid timelineRecordId, string type, string name, string path, bool deleteSource);
|
||||||
void QueueResultsUpload(Guid timelineRecordId, string name, string path, string type, bool deleteSource, bool finalize, bool firstBlock, long totalLines);
|
void QueueResultsUpload(Guid timelineRecordId, string name, string path, string type, bool deleteSource, bool finalize, bool firstBlock, long totalLines);
|
||||||
@@ -71,18 +69,13 @@ namespace GitHub.Runner.Common
|
|||||||
private Task[] _allDequeueTasks;
|
private Task[] _allDequeueTasks;
|
||||||
private readonly TaskCompletionSource<int> _jobCompletionSource = new();
|
private readonly TaskCompletionSource<int> _jobCompletionSource = new();
|
||||||
private readonly TaskCompletionSource<int> _jobRecordUpdated = new();
|
private readonly TaskCompletionSource<int> _jobRecordUpdated = new();
|
||||||
private readonly List<JobTelemetry> _jobTelemetries = new();
|
|
||||||
private bool _queueInProcess = false;
|
private bool _queueInProcess = false;
|
||||||
private bool _resultsServiceOnly = false;
|
private bool _resultsServiceOnly = false;
|
||||||
private Stopwatch _resultsUploadTimer = new();
|
|
||||||
private Stopwatch _actionsUploadTimer = new();
|
|
||||||
|
|
||||||
public TaskCompletionSource<int> JobRecordUpdated => _jobRecordUpdated;
|
public TaskCompletionSource<int> JobRecordUpdated => _jobRecordUpdated;
|
||||||
|
|
||||||
public event EventHandler<ThrottlingEventArgs> JobServerQueueThrottling;
|
public event EventHandler<ThrottlingEventArgs> JobServerQueueThrottling;
|
||||||
|
|
||||||
public IList<JobTelemetry> JobTelemetries => _jobTelemetries;
|
|
||||||
|
|
||||||
// Web console dequeue will start with process queue every 250ms for the first 60*4 times (~60 seconds).
|
// Web console dequeue will start with process queue every 250ms for the first 60*4 times (~60 seconds).
|
||||||
// Then the dequeue will happen every 500ms.
|
// Then the dequeue will happen every 500ms.
|
||||||
// In this way, customer still can get instance live console output on job start,
|
// In this way, customer still can get instance live console output on job start,
|
||||||
@@ -94,7 +87,6 @@ namespace GitHub.Runner.Common
|
|||||||
private bool _firstConsoleOutputs = true;
|
private bool _firstConsoleOutputs = true;
|
||||||
|
|
||||||
private bool _resultsClientInitiated = false;
|
private bool _resultsClientInitiated = false;
|
||||||
private bool _enableTelemetry = false;
|
|
||||||
private delegate Task ResultsFileUploadHandler(ResultsUploadFileInfo file);
|
private delegate Task ResultsFileUploadHandler(ResultsUploadFileInfo file);
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
public override void Initialize(IHostContext hostContext)
|
||||||
@@ -104,11 +96,10 @@ namespace GitHub.Runner.Common
|
|||||||
_resultsServer = hostContext.GetService<IResultsServer>();
|
_resultsServer = hostContext.GetService<IResultsServer>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start(Pipelines.AgentJobRequestMessage jobRequest, bool resultsServiceOnly = false, bool enableTelemetry = false)
|
public void Start(Pipelines.AgentJobRequestMessage jobRequest, bool resultsServiceOnly = false)
|
||||||
{
|
{
|
||||||
Trace.Entering();
|
Trace.Entering();
|
||||||
_resultsServiceOnly = resultsServiceOnly;
|
_resultsServiceOnly = resultsServiceOnly;
|
||||||
_enableTelemetry = enableTelemetry;
|
|
||||||
|
|
||||||
var serviceEndPoint = jobRequest.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
var serviceEndPoint = jobRequest.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
@@ -220,12 +211,6 @@ namespace GitHub.Runner.Common
|
|||||||
await _resultsServer.DisposeAsync();
|
await _resultsServer.DisposeAsync();
|
||||||
|
|
||||||
Trace.Info("All queue process tasks have been stopped, and all queues are drained.");
|
Trace.Info("All queue process tasks have been stopped, and all queues are drained.");
|
||||||
if (_enableTelemetry)
|
|
||||||
{
|
|
||||||
var uploadTimeComparison = $"Actions upload time: {_actionsUploadTimer.ElapsedMilliseconds} ms, Result upload time: {_resultsUploadTimer.ElapsedMilliseconds} ms";
|
|
||||||
Trace.Info(uploadTimeComparison);
|
|
||||||
_jobTelemetries.Add(new JobTelemetry() { Type = JobTelemetryType.General, Message = uploadTimeComparison });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void QueueWebConsoleLine(Guid stepRecordId, string line, long? lineNumber)
|
public void QueueWebConsoleLine(Guid stepRecordId, string line, long? lineNumber)
|
||||||
@@ -471,10 +456,6 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_enableTelemetry)
|
|
||||||
{
|
|
||||||
_actionsUploadTimer.Start();
|
|
||||||
}
|
|
||||||
await UploadFile(file);
|
await UploadFile(file);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -490,13 +471,6 @@ namespace GitHub.Runner.Common
|
|||||||
// _fileUploadQueue.Enqueue(file);
|
// _fileUploadQueue.Enqueue(file);
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (_enableTelemetry)
|
|
||||||
{
|
|
||||||
_actionsUploadTimer.Stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Trace.Info("Try to upload {0} log files or attachments, success rate: {1}/{0}.", filesToUpload.Count, filesToUpload.Count - errorCount);
|
Trace.Info("Try to upload {0} log files or attachments, success rate: {1}/{0}.", filesToUpload.Count, filesToUpload.Count - errorCount);
|
||||||
@@ -543,10 +517,6 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_enableTelemetry)
|
|
||||||
{
|
|
||||||
_resultsUploadTimer.Start();
|
|
||||||
}
|
|
||||||
if (String.Equals(file.Type, ChecksAttachmentType.StepSummary, StringComparison.OrdinalIgnoreCase))
|
if (String.Equals(file.Type, ChecksAttachmentType.StepSummary, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
await UploadSummaryFile(file);
|
await UploadSummaryFile(file);
|
||||||
@@ -578,13 +548,6 @@ namespace GitHub.Runner.Common
|
|||||||
SendResultsTelemetry(ex);
|
SendResultsTelemetry(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (_enableTelemetry)
|
|
||||||
{
|
|
||||||
_resultsUploadTimer.Stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Trace.Info("Tried to upload {0} file(s) to results, success rate: {1}/{0}.", filesToUpload.Count, filesToUpload.Count - errorCount);
|
Trace.Info("Tried to upload {0} file(s) to results, success rate: {1}/{0}.", filesToUpload.Count, filesToUpload.Count - errorCount);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
using System.Security;
|
using System.Security;
|
||||||
@@ -53,8 +52,8 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
public void InitializeResultsClient(Uri uri, string liveConsoleFeedUrl, string token)
|
public void InitializeResultsClient(Uri uri, string liveConsoleFeedUrl, string token)
|
||||||
{
|
{
|
||||||
this._resultsClient = CreateHttpClient(uri, token);
|
var httpMessageHandler = HostContext.CreateHttpClientHandler();
|
||||||
|
this._resultsClient = new ResultsHttpClient(uri, httpMessageHandler, token, disposeHandler: true);
|
||||||
_token = token;
|
_token = token;
|
||||||
if (!string.IsNullOrEmpty(liveConsoleFeedUrl))
|
if (!string.IsNullOrEmpty(liveConsoleFeedUrl))
|
||||||
{
|
{
|
||||||
@@ -63,26 +62,6 @@ namespace GitHub.Runner.Common
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResultsHttpClient CreateHttpClient(Uri uri, string token)
|
|
||||||
{
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
return new ResultsHttpClient(uri, pipeline, token, disposeHandler: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task CreateResultsStepSummaryAsync(string planId, string jobId, Guid stepId, string file,
|
public Task CreateResultsStepSummaryAsync(string planId, string jobId, Guid stepId, string file,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ namespace GitHub.Runner.Common
|
|||||||
}
|
}
|
||||||
catch (Exception ex) when (retry < maxRetryAttemptsCount && responseStatus != System.Net.HttpStatusCode.NotFound)
|
catch (Exception ex) when (retry < maxRetryAttemptsCount && responseStatus != System.Net.HttpStatusCode.NotFound)
|
||||||
{
|
{
|
||||||
Trace.Error($"{errorMessage} -- Attempt: {retry}");
|
Trace.Error($"{errorMessage} -- Atempt: {retry}");
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace GitHub.Runner.Common
|
|||||||
Task<TaskAgentSession> CreateAgentSessionAsync(Int32 poolId, TaskAgentSession session, CancellationToken cancellationToken);
|
Task<TaskAgentSession> CreateAgentSessionAsync(Int32 poolId, TaskAgentSession session, CancellationToken cancellationToken);
|
||||||
Task DeleteAgentMessageAsync(Int32 poolId, Int64 messageId, Guid sessionId, CancellationToken cancellationToken);
|
Task DeleteAgentMessageAsync(Int32 poolId, Int64 messageId, Guid sessionId, CancellationToken cancellationToken);
|
||||||
Task DeleteAgentSessionAsync(Int32 poolId, Guid sessionId, CancellationToken cancellationToken);
|
Task DeleteAgentSessionAsync(Int32 poolId, Guid sessionId, CancellationToken cancellationToken);
|
||||||
Task<TaskAgentMessage> GetAgentMessageAsync(Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, string runnerVersion, string os, string architecture, bool disableUpdate, CancellationToken cancellationToken);
|
Task<TaskAgentMessage> GetAgentMessageAsync(Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, string runnerVersion, CancellationToken cancellationToken);
|
||||||
|
|
||||||
// job request
|
// job request
|
||||||
Task<TaskAgentJobRequest> GetAgentRequestAsync(int poolId, long requestId, CancellationToken cancellationToken);
|
Task<TaskAgentJobRequest> GetAgentRequestAsync(int poolId, long requestId, CancellationToken cancellationToken);
|
||||||
@@ -272,10 +272,10 @@ namespace GitHub.Runner.Common
|
|||||||
return _messageTaskAgentClient.DeleteAgentSessionAsync(poolId, sessionId, cancellationToken: cancellationToken);
|
return _messageTaskAgentClient.DeleteAgentSessionAsync(poolId, sessionId, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<TaskAgentMessage> GetAgentMessageAsync(Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, string runnerVersion, string os, string architecture, bool disableUpdate, CancellationToken cancellationToken)
|
public Task<TaskAgentMessage> GetAgentMessageAsync(Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, string runnerVersion, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
CheckConnection(RunnerConnectionType.MessageQueue);
|
CheckConnection(RunnerConnectionType.MessageQueue);
|
||||||
return _messageTaskAgentClient.GetMessageAsync(poolId, sessionId, lastMessageId, status, runnerVersion, os, architecture, disableUpdate, cancellationToken: cancellationToken);
|
return _messageTaskAgentClient.GetMessageAsync(poolId, sessionId, lastMessageId, status, runnerVersion, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------
|
//-----------------------------------------------------------------
|
||||||
|
|||||||
@@ -19,13 +19,6 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
DefaultTraceLevel = TraceLevel.Verbose;
|
DefaultTraceLevel = TraceLevel.Verbose;
|
||||||
}
|
}
|
||||||
else if (int.TryParse(Environment.GetEnvironmentVariable("ACTIONS_RUNNER_TRACE_LEVEL"), out var traceLevel))
|
|
||||||
{
|
|
||||||
// force user's TraceLevel to comply with runner TraceLevel enums
|
|
||||||
traceLevel = Math.Clamp(traceLevel, 0, 5);
|
|
||||||
|
|
||||||
DefaultTraceLevel = (TraceLevel)traceLevel;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
@@ -12,8 +7,6 @@ using GitHub.Runner.Common;
|
|||||||
using GitHub.Runner.Listener.Configuration;
|
using GitHub.Runner.Listener.Configuration;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
using GitHub.Services.Common;
|
using GitHub.Services.Common;
|
||||||
using GitHub.Runner.Common.Util;
|
|
||||||
using GitHub.Services.OAuth;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Listener
|
namespace GitHub.Runner.Listener
|
||||||
{
|
{
|
||||||
@@ -26,6 +19,8 @@ namespace GitHub.Runner.Listener
|
|||||||
private CancellationTokenSource _getMessagesTokenSource;
|
private CancellationTokenSource _getMessagesTokenSource;
|
||||||
private IBrokerServer _brokerServer;
|
private IBrokerServer _brokerServer;
|
||||||
|
|
||||||
|
public string _sessionId;
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
public override void Initialize(IHostContext hostContext)
|
||||||
{
|
{
|
||||||
base.Initialize(hostContext);
|
base.Initialize(hostContext);
|
||||||
@@ -36,7 +31,7 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
public async Task<Boolean> CreateSessionAsync(CancellationToken token)
|
public async Task<Boolean> CreateSessionAsync(CancellationToken token)
|
||||||
{
|
{
|
||||||
await RefreshBrokerConnection();
|
await RefreshBrokerSession(token);
|
||||||
return await Task.FromResult(true);
|
return await Task.FromResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,12 +68,7 @@ namespace GitHub.Runner.Listener
|
|||||||
_getMessagesTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
|
_getMessagesTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
message = await _brokerServer.GetRunnerMessageAsync(_getMessagesTokenSource.Token,
|
message = await _brokerServer.GetRunnerMessageAsync(_getMessagesTokenSource.Token, runnerStatus, BuildConstants.RunnerPackage.Version);
|
||||||
runnerStatus,
|
|
||||||
BuildConstants.RunnerPackage.Version,
|
|
||||||
VarUtil.OS,
|
|
||||||
VarUtil.OSArchitecture,
|
|
||||||
_settings.DisableUpdate);
|
|
||||||
|
|
||||||
if (message == null)
|
if (message == null)
|
||||||
{
|
{
|
||||||
@@ -142,8 +132,8 @@ namespace GitHub.Runner.Listener
|
|||||||
encounteringError = true;
|
encounteringError = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// re-create VssConnection before next retry
|
// re-create session before next retry
|
||||||
await RefreshBrokerConnection();
|
await RefreshBrokerSession(token);
|
||||||
|
|
||||||
Trace.Info("Sleeping for {0} seconds before retrying.", _getNextMessageRetryInterval.TotalSeconds);
|
Trace.Info("Sleeping for {0} seconds before retrying.", _getNextMessageRetryInterval.TotalSeconds);
|
||||||
await HostContext.Delay(_getNextMessageRetryInterval, token);
|
await HostContext.Delay(_getNextMessageRetryInterval, token);
|
||||||
@@ -196,7 +186,7 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RefreshBrokerConnection()
|
private async Task RefreshBrokerSession(CancellationToken ct)
|
||||||
{
|
{
|
||||||
var configManager = HostContext.GetService<IConfigurationManager>();
|
var configManager = HostContext.GetService<IConfigurationManager>();
|
||||||
_settings = configManager.LoadSettings();
|
_settings = configManager.LoadSettings();
|
||||||
@@ -208,7 +198,8 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
var credMgr = HostContext.GetService<ICredentialManager>();
|
||||||
VssCredentials creds = credMgr.LoadCredentials();
|
VssCredentials creds = credMgr.LoadCredentials();
|
||||||
await _brokerServer.ConnectAsync(new Uri(_settings.ServerUrlV2), creds);
|
var session = await _brokerServer.CreateSessionAsync(new Uri(_settings.ServerUrlV2), creds, ct);
|
||||||
|
_sessionId = session.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -744,7 +744,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
catch (Exception ex) when (retryCount < 2 && responseStatus != System.Net.HttpStatusCode.NotFound)
|
catch (Exception ex) when (retryCount < 2 && responseStatus != System.Net.HttpStatusCode.NotFound)
|
||||||
{
|
{
|
||||||
retryCount++;
|
retryCount++;
|
||||||
Trace.Error($"Failed to get JIT runner token -- Attempt: {retryCount}");
|
Trace.Error($"Failed to get JIT runner token -- Atempt: {retryCount}");
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -807,7 +807,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
catch (Exception ex) when (retryCount < 2 && responseStatus != System.Net.HttpStatusCode.NotFound)
|
catch (Exception ex) when (retryCount < 2 && responseStatus != System.Net.HttpStatusCode.NotFound)
|
||||||
{
|
{
|
||||||
retryCount++;
|
retryCount++;
|
||||||
Trace.Error($"Failed to get tenant credentials -- Attempt: {retryCount}");
|
Trace.Error($"Failed to get tenant credentials -- Atempt: {retryCount}");
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
|
|
||||||
if (!store.HasCredentials())
|
if (!store.HasCredentials())
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Credentials not stored. Must reconfigure.");
|
throw new InvalidOperationException("Credentials not stored. Must reconfigure.");
|
||||||
}
|
}
|
||||||
|
|
||||||
CredentialData credData = store.GetCredentials();
|
CredentialData credData = store.GetCredentials();
|
||||||
|
|||||||
@@ -514,25 +514,9 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
failureActions.Add(new FailureAction(RecoverAction.Restart, 60000));
|
failureActions.Add(new FailureAction(RecoverAction.Restart, 60000));
|
||||||
|
|
||||||
// Lock the Service Database
|
// Lock the Service Database
|
||||||
int svcLockRetries = 10;
|
svcLock = LockServiceDatabase(scmHndl);
|
||||||
int svcLockRetryTimeout = 5000;
|
if (svcLock.ToInt64() <= 0)
|
||||||
while (true)
|
|
||||||
{
|
{
|
||||||
svcLock = LockServiceDatabase(scmHndl);
|
|
||||||
if (svcLock.ToInt64() > 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
_term.WriteLine("Retrying Lock Service Database...");
|
|
||||||
|
|
||||||
svcLockRetries--;
|
|
||||||
if (svcLockRetries > 0)
|
|
||||||
{
|
|
||||||
Thread.Sleep(svcLockRetryTimeout);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception("Failed to Lock Service Database for Write");
|
throw new Exception("Failed to Lock Service Database for Write");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ namespace GitHub.Runner.Listener
|
|||||||
Guid dispatchedJobId = _jobDispatchedQueue.Dequeue();
|
Guid dispatchedJobId = _jobDispatchedQueue.Dequeue();
|
||||||
if (_jobInfos.TryGetValue(dispatchedJobId, out currentDispatch))
|
if (_jobInfos.TryGetValue(dispatchedJobId, out currentDispatch))
|
||||||
{
|
{
|
||||||
Trace.Verbose($"Retrive previous WorkerDispatcher for job {currentDispatch.JobId}.");
|
Trace.Verbose($"Retrive previous WorkerDispather for job {currentDispatch.JobId}.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,12 +162,12 @@ namespace GitHub.Runner.Listener
|
|||||||
dispatchedJobId = _jobDispatchedQueue.Dequeue();
|
dispatchedJobId = _jobDispatchedQueue.Dequeue();
|
||||||
if (_jobInfos.TryGetValue(dispatchedJobId, out currentDispatch))
|
if (_jobInfos.TryGetValue(dispatchedJobId, out currentDispatch))
|
||||||
{
|
{
|
||||||
Trace.Verbose($"Retrive previous WorkerDispatcher for job {currentDispatch.JobId}.");
|
Trace.Verbose($"Retrive previous WorkerDispather for job {currentDispatch.JobId}.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Trace.Verbose($"There is no running WorkerDispatcher needs to await.");
|
Trace.Verbose($"There is no running WorkerDispather needs to await.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentDispatch != null)
|
if (currentDispatch != null)
|
||||||
@@ -176,7 +176,7 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Trace.Info($"Waiting WorkerDispatcher for job {currentDispatch.JobId} run to finish.");
|
Trace.Info($"Waiting WorkerDispather for job {currentDispatch.JobId} run to finish.");
|
||||||
await currentDispatch.WorkerDispatch;
|
await currentDispatch.WorkerDispatch;
|
||||||
Trace.Info($"Job request {currentDispatch.JobId} processed succeed.");
|
Trace.Info($"Job request {currentDispatch.JobId} processed succeed.");
|
||||||
}
|
}
|
||||||
@@ -190,7 +190,7 @@ namespace GitHub.Runner.Listener
|
|||||||
WorkerDispatcher workerDispatcher;
|
WorkerDispatcher workerDispatcher;
|
||||||
if (_jobInfos.TryRemove(currentDispatch.JobId, out workerDispatcher))
|
if (_jobInfos.TryRemove(currentDispatch.JobId, out workerDispatcher))
|
||||||
{
|
{
|
||||||
Trace.Verbose($"Remove WorkerDispatcher from {nameof(_jobInfos)} dictionary for job {currentDispatch.JobId}.");
|
Trace.Verbose($"Remove WorkerDispather from {nameof(_jobInfos)} dictionary for job {currentDispatch.JobId}.");
|
||||||
workerDispatcher.Dispose();
|
workerDispatcher.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -209,7 +209,7 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Trace.Info($"Ensure WorkerDispatcher for job {currentDispatch.JobId} run to finish, cancel any running job.");
|
Trace.Info($"Ensure WorkerDispather for job {currentDispatch.JobId} run to finish, cancel any running job.");
|
||||||
await EnsureDispatchFinished(currentDispatch, cancelRunningJob: true);
|
await EnsureDispatchFinished(currentDispatch, cancelRunningJob: true);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -222,7 +222,7 @@ namespace GitHub.Runner.Listener
|
|||||||
WorkerDispatcher workerDispatcher;
|
WorkerDispatcher workerDispatcher;
|
||||||
if (_jobInfos.TryRemove(currentDispatch.JobId, out workerDispatcher))
|
if (_jobInfos.TryRemove(currentDispatch.JobId, out workerDispatcher))
|
||||||
{
|
{
|
||||||
Trace.Verbose($"Remove WorkerDispatcher from {nameof(_jobInfos)} dictionary for job {currentDispatch.JobId}.");
|
Trace.Verbose($"Remove WorkerDispather from {nameof(_jobInfos)} dictionary for job {currentDispatch.JobId}.");
|
||||||
workerDispatcher.Dispose();
|
workerDispatcher.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -327,7 +327,7 @@ namespace GitHub.Runner.Listener
|
|||||||
WorkerDispatcher workerDispatcher;
|
WorkerDispatcher workerDispatcher;
|
||||||
if (_jobInfos.TryRemove(jobDispatch.JobId, out workerDispatcher))
|
if (_jobInfos.TryRemove(jobDispatch.JobId, out workerDispatcher))
|
||||||
{
|
{
|
||||||
Trace.Verbose($"Remove WorkerDispatcher from {nameof(_jobInfos)} dictionary for job {jobDispatch.JobId}.");
|
Trace.Verbose($"Remove WorkerDispather from {nameof(_jobInfos)} dictionary for job {jobDispatch.JobId}.");
|
||||||
workerDispatcher.Dispose();
|
workerDispatcher.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
using GitHub.Runner.Common.Util;
|
|
||||||
using GitHub.Runner.Listener.Configuration;
|
using GitHub.Runner.Listener.Configuration;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
using GitHub.Services.Common;
|
using GitHub.Services.Common;
|
||||||
@@ -129,7 +128,7 @@ namespace GitHub.Runner.Listener
|
|||||||
// "invalid_client" means the runner registration has been deleted from the server.
|
// "invalid_client" means the runner registration has been deleted from the server.
|
||||||
if (string.Equals(vssOAuthEx.Error, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(vssOAuthEx.Error, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
_term.WriteError("Failed to create a session. The runner registration has been deleted from the server, please re-configure. Runner registrations are automatically deleted for runners that have not connected to the service recently.");
|
_term.WriteError("Failed to create a session. The runner registration has been deleted from the server, please re-configure.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +139,7 @@ namespace GitHub.Runner.Listener
|
|||||||
var authError = await oauthTokenProvider.ValidateCredentialAsync(token);
|
var authError = await oauthTokenProvider.ValidateCredentialAsync(token);
|
||||||
if (string.Equals(authError, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(authError, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
_term.WriteError("Failed to create a session. The runner registration has been deleted from the server, please re-configure. Runner registrations are automatically deleted for runners that have not connected to the service recently.");
|
_term.WriteError("Failed to create a session. The runner registration has been deleted from the server, please re-configure.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,9 +219,6 @@ namespace GitHub.Runner.Listener
|
|||||||
_lastMessageId,
|
_lastMessageId,
|
||||||
runnerStatus,
|
runnerStatus,
|
||||||
BuildConstants.RunnerPackage.Version,
|
BuildConstants.RunnerPackage.Version,
|
||||||
VarUtil.OS,
|
|
||||||
VarUtil.OSArchitecture,
|
|
||||||
_settings.DisableUpdate,
|
|
||||||
_getMessagesTokenSource.Token);
|
_getMessagesTokenSource.Token);
|
||||||
|
|
||||||
// Decrypt the message body if the session is using encryption
|
// Decrypt the message body if the session is using encryption
|
||||||
|
|||||||
@@ -457,13 +457,22 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
message = await getNextMessage; //get next message
|
message = await getNextMessage; //get next message
|
||||||
HostContext.WritePerfCounter($"MessageReceived_{message.MessageType}");
|
HostContext.WritePerfCounter($"MessageReceived_{message.MessageType}");
|
||||||
if (string.Equals(message.MessageType, AgentRefreshMessage.MessageType, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(message.MessageType, AgentRefreshMessage.MessageType, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(message.MessageType, RunnerRefreshMessage.MessageType, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (autoUpdateInProgress == false)
|
if (autoUpdateInProgress == false)
|
||||||
{
|
{
|
||||||
autoUpdateInProgress = true;
|
autoUpdateInProgress = true;
|
||||||
AgentRefreshMessage runnerUpdateMessage = JsonUtility.FromString<AgentRefreshMessage>(message.Body);
|
AgentRefreshMessage runnerUpdateMessage = null;
|
||||||
|
if (string.Equals(message.MessageType, AgentRefreshMessage.MessageType, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
runnerUpdateMessage = JsonUtility.FromString<AgentRefreshMessage>(message.Body);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var brokerRunnerUpdateMessage = JsonUtility.FromString<RunnerRefreshMessage>(message.Body);
|
||||||
|
runnerUpdateMessage = new AgentRefreshMessage(brokerRunnerUpdateMessage.RunnerId, brokerRunnerUpdateMessage.TargetVersion, TimeSpan.FromSeconds(brokerRunnerUpdateMessage.TimeoutInSeconds));
|
||||||
|
}
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
// Can mock the update for testing
|
// Can mock the update for testing
|
||||||
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_IS_MOCK_UPDATE")))
|
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_IS_MOCK_UPDATE")))
|
||||||
@@ -494,22 +503,6 @@ namespace GitHub.Runner.Listener
|
|||||||
Trace.Info("Refresh message received, skip autoupdate since a previous autoupdate is already running.");
|
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);
|
|
||||||
|
|
||||||
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))
|
else if (string.Equals(message.MessageType, JobRequestMessageTypes.PipelineAgentJobRequest, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (autoUpdateInProgress || runOnceJobReceived)
|
if (autoUpdateInProgress || runOnceJobReceived)
|
||||||
|
|||||||
@@ -1,569 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.IO.Compression;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
using GitHub.Runner.Common;
|
|
||||||
using GitHub.Runner.Common.Util;
|
|
||||||
using GitHub.Runner.Sdk;
|
|
||||||
using GitHub.Services.Common;
|
|
||||||
using GitHub.Services.WebApi;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Listener
|
|
||||||
{
|
|
||||||
// This class is a fork of SelfUpdater.cs and is intended to only be used for the
|
|
||||||
// new self-update flow where the PackageMetadata is sent in the message directly.
|
|
||||||
// Forking the class prevents us from accidentally breaking the old flow while it's still in production
|
|
||||||
|
|
||||||
[ServiceLocator(Default = typeof(SelfUpdaterV2))]
|
|
||||||
public interface ISelfUpdaterV2 : IRunnerService
|
|
||||||
{
|
|
||||||
bool Busy { get; }
|
|
||||||
Task<bool> SelfUpdate(RunnerRefreshMessage updateMessage, IJobDispatcher jobDispatcher, bool restartInteractiveRunner, CancellationToken token);
|
|
||||||
}
|
|
||||||
public class SelfUpdaterV2 : RunnerService, ISelfUpdaterV2
|
|
||||||
{
|
|
||||||
private static string _platform = BuildConstants.RunnerPackage.PackageName;
|
|
||||||
private ITerminal _terminal;
|
|
||||||
private IRunnerServer _runnerServer;
|
|
||||||
private int _poolId;
|
|
||||||
private ulong _agentId;
|
|
||||||
|
|
||||||
private const int _numberOfOldVersionsToKeep = 1;
|
|
||||||
|
|
||||||
private readonly ConcurrentQueue<string> _updateTrace = new();
|
|
||||||
public bool Busy { get; private set; }
|
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
|
||||||
{
|
|
||||||
base.Initialize(hostContext);
|
|
||||||
|
|
||||||
_terminal = hostContext.GetService<ITerminal>();
|
|
||||||
_runnerServer = HostContext.GetService<IRunnerServer>();
|
|
||||||
var configStore = HostContext.GetService<IConfigurationStore>();
|
|
||||||
var settings = configStore.GetSettings();
|
|
||||||
_poolId = settings.PoolId;
|
|
||||||
_agentId = settings.AgentId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> SelfUpdate(RunnerRefreshMessage updateMessage, IJobDispatcher jobDispatcher, bool restartInteractiveRunner, CancellationToken token)
|
|
||||||
{
|
|
||||||
Busy = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var totalUpdateTime = Stopwatch.StartNew();
|
|
||||||
|
|
||||||
Trace.Info($"An update is available.");
|
|
||||||
_updateTrace.Enqueue($"RunnerPlatform: {updateMessage.OS}");
|
|
||||||
|
|
||||||
// Print console line that warn user not shutdown runner.
|
|
||||||
_terminal.WriteLine("Runner update in progress, do not shutdown runner.");
|
|
||||||
_terminal.WriteLine($"Downloading {updateMessage.TargetVersion} runner");
|
|
||||||
|
|
||||||
await DownloadLatestRunner(token, updateMessage.TargetVersion, updateMessage.DownloadUrl, updateMessage.SHA256Checksum, updateMessage.OS);
|
|
||||||
Trace.Info($"Download latest runner and unzip into runner root.");
|
|
||||||
|
|
||||||
// wait till all running job finish
|
|
||||||
_terminal.WriteLine("Waiting for current job finish running.");
|
|
||||||
|
|
||||||
await jobDispatcher.WaitAsync(token);
|
|
||||||
Trace.Info($"All running job has exited.");
|
|
||||||
|
|
||||||
// We need to keep runner backup around for macOS until we fixed https://github.com/actions/runner/issues/743
|
|
||||||
// delete runner backup
|
|
||||||
var stopWatch = Stopwatch.StartNew();
|
|
||||||
DeletePreviousVersionRunnerBackup(token, updateMessage.TargetVersion);
|
|
||||||
Trace.Info($"Delete old version runner backup.");
|
|
||||||
stopWatch.Stop();
|
|
||||||
// generate update script from template
|
|
||||||
_updateTrace.Enqueue($"DeleteRunnerBackupTime: {stopWatch.ElapsedMilliseconds}ms");
|
|
||||||
_terminal.WriteLine("Generate and execute update script.");
|
|
||||||
|
|
||||||
string updateScript = GenerateUpdateScript(restartInteractiveRunner, updateMessage.TargetVersion);
|
|
||||||
Trace.Info($"Generate update script into: {updateScript}");
|
|
||||||
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
// For L0, we will skip execute update script.
|
|
||||||
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GITHUB_ACTION_EXECUTE_UPDATE_SCRIPT")))
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
string flagFile = "update.finished";
|
|
||||||
IOUtil.DeleteFile(flagFile);
|
|
||||||
// kick off update script
|
|
||||||
Process invokeScript = new();
|
|
||||||
#if OS_WINDOWS
|
|
||||||
invokeScript.StartInfo.FileName = WhichUtil.Which("cmd.exe", trace: Trace);
|
|
||||||
invokeScript.StartInfo.Arguments = $"/c \"{updateScript}\"";
|
|
||||||
#elif (OS_OSX || OS_LINUX)
|
|
||||||
invokeScript.StartInfo.FileName = WhichUtil.Which("bash", trace: Trace);
|
|
||||||
invokeScript.StartInfo.Arguments = $"\"{updateScript}\"";
|
|
||||||
#endif
|
|
||||||
invokeScript.Start();
|
|
||||||
Trace.Info($"Update script start running");
|
|
||||||
}
|
|
||||||
|
|
||||||
totalUpdateTime.Stop();
|
|
||||||
|
|
||||||
_updateTrace.Enqueue($"TotalUpdateTime: {totalUpdateTime.ElapsedMilliseconds}ms");
|
|
||||||
_terminal.WriteLine("Runner will exit shortly for update, should be back online within 10 seconds.");
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_updateTrace.Enqueue(ex.ToString());
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_terminal.WriteLine("Runner update process finished.");
|
|
||||||
Busy = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// _work
|
|
||||||
/// \_update
|
|
||||||
/// \bin
|
|
||||||
/// \externals
|
|
||||||
/// \run.sh
|
|
||||||
/// \run.cmd
|
|
||||||
/// \package.zip //temp download .zip/.tar.gz
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="token"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private async Task DownloadLatestRunner(CancellationToken token, string targetVersion, string packageDownloadUrl, string packageHashValue, string targetPlatform)
|
|
||||||
{
|
|
||||||
string latestRunnerDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), Constants.Path.UpdateDirectory);
|
|
||||||
IOUtil.DeleteDirectory(latestRunnerDirectory, token);
|
|
||||||
Directory.CreateDirectory(latestRunnerDirectory);
|
|
||||||
|
|
||||||
string archiveFile = null;
|
|
||||||
|
|
||||||
// Only try trimmed package if sever sends them and we have calculated hash value of the current runtime/externals.
|
|
||||||
_updateTrace.Enqueue($"DownloadUrl: {packageDownloadUrl}");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
// Much of the update process (targetVersion, archive) is server-side, this is a way to control it from here for testing specific update scenarios
|
|
||||||
// Add files like 'runner2.281.2.tar.gz' or 'runner2.283.0.zip' (depending on your platform) to your runner root folder
|
|
||||||
// Note that runners still need to be older than the server's runner version in order to receive an 'AgentRefreshMessage' and trigger this update
|
|
||||||
// Wrapped in #if DEBUG as this should not be in the RELEASE build
|
|
||||||
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_IS_MOCK_UPDATE")))
|
|
||||||
{
|
|
||||||
var waitForDebugger = StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_IS_MOCK_UPDATE_WAIT_FOR_DEBUGGER"));
|
|
||||||
if (waitForDebugger)
|
|
||||||
{
|
|
||||||
int waitInSeconds = 20;
|
|
||||||
while (!Debugger.IsAttached && waitInSeconds-- > 0)
|
|
||||||
{
|
|
||||||
await Task.Delay(1000);
|
|
||||||
}
|
|
||||||
Debugger.Break();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetPlatform.StartsWith("win"))
|
|
||||||
{
|
|
||||||
archiveFile = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"runner{targetVersion}.zip");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
archiveFile = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"runner{targetVersion}.tar.gz");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (File.Exists(archiveFile))
|
|
||||||
{
|
|
||||||
_updateTrace.Enqueue($"Mocking update with file: '{archiveFile}' and targetVersion: '{targetVersion}', nothing is downloaded");
|
|
||||||
_terminal.WriteLine($"Mocking update with file: '{archiveFile}' and targetVersion: '{targetVersion}', nothing is downloaded");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
archiveFile = null;
|
|
||||||
_terminal.WriteLine($"Mock runner archive not found at {archiveFile} for target version {targetVersion}, proceeding with download instead");
|
|
||||||
_updateTrace.Enqueue($"Mock runner archive not found at {archiveFile} for target version {targetVersion}, proceeding with download instead");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
// archiveFile is not null only if we mocked it above
|
|
||||||
if (string.IsNullOrEmpty(archiveFile))
|
|
||||||
{
|
|
||||||
archiveFile = await DownLoadRunner(latestRunnerDirectory, packageDownloadUrl, packageHashValue, targetPlatform, token);
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(archiveFile))
|
|
||||||
{
|
|
||||||
throw new TaskCanceledException($"Runner package '{packageDownloadUrl}' failed after {Constants.RunnerDownloadRetryMaxAttempts} download attempts");
|
|
||||||
}
|
|
||||||
await ValidateRunnerHash(archiveFile, packageHashValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
await ExtractRunnerPackage(archiveFile, latestRunnerDirectory, token);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// delete .zip file
|
|
||||||
if (!string.IsNullOrEmpty(archiveFile) && File.Exists(archiveFile))
|
|
||||||
{
|
|
||||||
Trace.Verbose("Deleting latest runner package zip: {0}", archiveFile);
|
|
||||||
IOUtil.DeleteFile(archiveFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
//it is not critical if we fail to delete the .zip file
|
|
||||||
Trace.Warning("Failed to delete runner package zip '{0}'. Exception: {1}", archiveFile, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await CopyLatestRunnerToRoot(latestRunnerDirectory, targetVersion, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<string> DownLoadRunner(string downloadDirectory, string packageDownloadUrl, string packageHashValue, string packagePlatform, CancellationToken token)
|
|
||||||
{
|
|
||||||
var stopWatch = Stopwatch.StartNew();
|
|
||||||
int runnerSuffix = 1;
|
|
||||||
string archiveFile = null;
|
|
||||||
bool downloadSucceeded = false;
|
|
||||||
|
|
||||||
// Download the runner, using multiple attempts in order to be resilient against any networking/CDN issues
|
|
||||||
for (int attempt = 1; attempt <= Constants.RunnerDownloadRetryMaxAttempts; attempt++)
|
|
||||||
{
|
|
||||||
// Generate an available package name, and do our best effort to clean up stale local zip files
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (packagePlatform.StartsWith("win"))
|
|
||||||
{
|
|
||||||
archiveFile = Path.Combine(downloadDirectory, $"runner{runnerSuffix}.zip");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
archiveFile = Path.Combine(downloadDirectory, $"runner{runnerSuffix}.tar.gz");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// delete .zip file
|
|
||||||
if (!string.IsNullOrEmpty(archiveFile) && File.Exists(archiveFile))
|
|
||||||
{
|
|
||||||
Trace.Verbose("Deleting latest runner package zip '{0}'", archiveFile);
|
|
||||||
IOUtil.DeleteFile(archiveFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// couldn't delete the file for whatever reason, so generate another name
|
|
||||||
Trace.Warning("Failed to delete runner package zip '{0}'. Exception: {1}", archiveFile, ex);
|
|
||||||
runnerSuffix++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow a 15-minute package download timeout, which is good enough to update the runner from a 1 Mbit/s ADSL connection.
|
|
||||||
if (!int.TryParse(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_DOWNLOAD_TIMEOUT") ?? string.Empty, out int timeoutSeconds))
|
|
||||||
{
|
|
||||||
timeoutSeconds = 15 * 60;
|
|
||||||
}
|
|
||||||
|
|
||||||
Trace.Info($"Attempt {attempt}: save latest runner into {archiveFile}.");
|
|
||||||
|
|
||||||
using (var downloadTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds)))
|
|
||||||
using (var downloadCts = CancellationTokenSource.CreateLinkedTokenSource(downloadTimeout.Token, token))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Trace.Info($"Download runner: begin download");
|
|
||||||
long downloadSize = 0;
|
|
||||||
|
|
||||||
//open zip stream in async mode
|
|
||||||
using (HttpClient httpClient = new(HostContext.CreateHttpClientHandler()))
|
|
||||||
{
|
|
||||||
Trace.Info($"Downloading {packageDownloadUrl}");
|
|
||||||
|
|
||||||
using (FileStream fs = new(archiveFile, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true))
|
|
||||||
using (Stream result = await httpClient.GetStreamAsync(packageDownloadUrl))
|
|
||||||
{
|
|
||||||
//81920 is the default used by System.IO.Stream.CopyTo and is under the large object heap threshold (85k).
|
|
||||||
await result.CopyToAsync(fs, 81920, downloadCts.Token);
|
|
||||||
await fs.FlushAsync(downloadCts.Token);
|
|
||||||
downloadSize = fs.Length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Trace.Info($"Download runner: finished download");
|
|
||||||
downloadSucceeded = true;
|
|
||||||
stopWatch.Stop();
|
|
||||||
_updateTrace.Enqueue($"PackageDownloadTime: {stopWatch.ElapsedMilliseconds}ms");
|
|
||||||
_updateTrace.Enqueue($"Attempts: {attempt}");
|
|
||||||
_updateTrace.Enqueue($"PackageSize: {downloadSize / 1024 / 1024}MB");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
Trace.Info($"Runner download has been cancelled.");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
if (downloadCts.Token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
Trace.Warning($"Runner download has timed out after {timeoutSeconds} seconds");
|
|
||||||
}
|
|
||||||
|
|
||||||
Trace.Warning($"Failed to get package '{archiveFile}' from '{packageDownloadUrl}'. Exception {ex}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (downloadSucceeded)
|
|
||||||
{
|
|
||||||
return archiveFile;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ValidateRunnerHash(string archiveFile, string packageHashValue)
|
|
||||||
{
|
|
||||||
var stopWatch = Stopwatch.StartNew();
|
|
||||||
// Validate Hash Matches if it is provided
|
|
||||||
using (FileStream stream = File.OpenRead(archiveFile))
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(packageHashValue))
|
|
||||||
{
|
|
||||||
using (SHA256 sha256 = SHA256.Create())
|
|
||||||
{
|
|
||||||
byte[] srcHashBytes = await sha256.ComputeHashAsync(stream);
|
|
||||||
var hash = PrimitiveExtensions.ConvertToHexString(srcHashBytes);
|
|
||||||
if (hash != packageHashValue)
|
|
||||||
{
|
|
||||||
// Hash did not match, we can't recover from this, just throw
|
|
||||||
throw new Exception($"Computed runner hash {hash} did not match expected Runner Hash {packageHashValue} for {archiveFile}");
|
|
||||||
}
|
|
||||||
|
|
||||||
stopWatch.Stop();
|
|
||||||
Trace.Info($"Validated Runner Hash matches {archiveFile} : {packageHashValue}");
|
|
||||||
_updateTrace.Enqueue($"ValidateHashTime: {stopWatch.ElapsedMilliseconds}ms");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ExtractRunnerPackage(string archiveFile, string extractDirectory, CancellationToken token)
|
|
||||||
{
|
|
||||||
var stopWatch = Stopwatch.StartNew();
|
|
||||||
|
|
||||||
if (archiveFile.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
ZipFile.ExtractToDirectory(archiveFile, extractDirectory);
|
|
||||||
}
|
|
||||||
else if (archiveFile.EndsWith(".tar.gz", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
string tar = WhichUtil.Which("tar", trace: Trace);
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(tar))
|
|
||||||
{
|
|
||||||
throw new NotSupportedException($"tar -xzf");
|
|
||||||
}
|
|
||||||
|
|
||||||
// tar -xzf
|
|
||||||
using (var processInvoker = HostContext.CreateService<IProcessInvoker>())
|
|
||||||
{
|
|
||||||
processInvoker.OutputDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(args.Data))
|
|
||||||
{
|
|
||||||
Trace.Info(args.Data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
processInvoker.ErrorDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(args.Data))
|
|
||||||
{
|
|
||||||
Trace.Error(args.Data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
int exitCode = await processInvoker.ExecuteAsync(extractDirectory, tar, $"-xzf \"{archiveFile}\"", null, token);
|
|
||||||
if (exitCode != 0)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException($"Can't use 'tar -xzf' to extract archive file: {archiveFile}. return code: {exitCode}.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new NotSupportedException($"{archiveFile}");
|
|
||||||
}
|
|
||||||
|
|
||||||
stopWatch.Stop();
|
|
||||||
Trace.Info($"Finished getting latest runner package at: {extractDirectory}.");
|
|
||||||
_updateTrace.Enqueue($"PackageExtractTime: {stopWatch.ElapsedMilliseconds}ms");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task CopyLatestRunnerToRoot(string latestRunnerDirectory, string targetVersion, CancellationToken token)
|
|
||||||
{
|
|
||||||
var stopWatch = Stopwatch.StartNew();
|
|
||||||
// copy latest runner into runner root folder
|
|
||||||
// copy bin from _work/_update -> bin.version under root
|
|
||||||
string binVersionDir = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"{Constants.Path.BinDirectory}.{targetVersion}");
|
|
||||||
Directory.CreateDirectory(binVersionDir);
|
|
||||||
Trace.Info($"Copy {Path.Combine(latestRunnerDirectory, Constants.Path.BinDirectory)} to {binVersionDir}.");
|
|
||||||
IOUtil.CopyDirectory(Path.Combine(latestRunnerDirectory, Constants.Path.BinDirectory), binVersionDir, token);
|
|
||||||
|
|
||||||
// copy externals from _work/_update -> externals.version under root
|
|
||||||
string externalsVersionDir = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"{Constants.Path.ExternalsDirectory}.{targetVersion}");
|
|
||||||
Directory.CreateDirectory(externalsVersionDir);
|
|
||||||
Trace.Info($"Copy {Path.Combine(latestRunnerDirectory, Constants.Path.ExternalsDirectory)} to {externalsVersionDir}.");
|
|
||||||
IOUtil.CopyDirectory(Path.Combine(latestRunnerDirectory, Constants.Path.ExternalsDirectory), externalsVersionDir, token);
|
|
||||||
|
|
||||||
// copy and replace all .sh/.cmd files
|
|
||||||
Trace.Info($"Copy any remaining .sh/.cmd files into runner root.");
|
|
||||||
foreach (FileInfo file in new DirectoryInfo(latestRunnerDirectory).GetFiles() ?? new FileInfo[0])
|
|
||||||
{
|
|
||||||
string destination = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), file.Name);
|
|
||||||
|
|
||||||
// Removing the file instead of just trying to overwrite it works around permissions issues on linux.
|
|
||||||
// https://github.com/actions/runner/issues/981
|
|
||||||
Trace.Info($"Copy {file.FullName} to {destination}");
|
|
||||||
IOUtil.DeleteFile(destination);
|
|
||||||
file.CopyTo(destination, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
stopWatch.Stop();
|
|
||||||
_updateTrace.Enqueue($"CopyRunnerToRootTime: {stopWatch.ElapsedMilliseconds}ms");
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DeletePreviousVersionRunnerBackup(CancellationToken token, string targetVersion)
|
|
||||||
{
|
|
||||||
// delete previous backup runner (back compat, can be remove after serval sprints)
|
|
||||||
// bin.bak.2.99.0
|
|
||||||
// externals.bak.2.99.0
|
|
||||||
foreach (string existBackUp in Directory.GetDirectories(HostContext.GetDirectory(WellKnownDirectory.Root), "*.bak.*"))
|
|
||||||
{
|
|
||||||
Trace.Info($"Delete existing runner backup at {existBackUp}.");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
IOUtil.DeleteDirectory(existBackUp, token);
|
|
||||||
}
|
|
||||||
catch (Exception ex) when (!(ex is OperationCanceledException))
|
|
||||||
{
|
|
||||||
Trace.Error(ex);
|
|
||||||
Trace.Info($"Catch exception during delete backup folder {existBackUp}, ignore this error try delete the backup folder on next auto-update.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete old bin.2.99.0 folder, only leave the current version and the latest download version
|
|
||||||
var allBinDirs = Directory.GetDirectories(HostContext.GetDirectory(WellKnownDirectory.Root), "bin.*");
|
|
||||||
if (allBinDirs.Length > _numberOfOldVersionsToKeep)
|
|
||||||
{
|
|
||||||
// there are more than {_numberOfOldVersionsToKeep} bin.version folder.
|
|
||||||
// delete older bin.version folders.
|
|
||||||
foreach (var oldBinDir in allBinDirs)
|
|
||||||
{
|
|
||||||
if (string.Equals(oldBinDir, Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"bin"), StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
string.Equals(oldBinDir, Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"bin.{BuildConstants.RunnerPackage.Version}"), StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
string.Equals(oldBinDir, Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"bin.{targetVersion}"), StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
// skip for current runner version
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Trace.Info($"Delete runner bin folder's backup at {oldBinDir}.");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
IOUtil.DeleteDirectory(oldBinDir, token);
|
|
||||||
}
|
|
||||||
catch (Exception ex) when (!(ex is OperationCanceledException))
|
|
||||||
{
|
|
||||||
Trace.Error(ex);
|
|
||||||
Trace.Info($"Catch exception during delete backup folder {oldBinDir}, ignore this error try delete the backup folder on next auto-update.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete old externals.2.99.0 folder, only leave the current version and the latest download version
|
|
||||||
var allExternalsDirs = Directory.GetDirectories(HostContext.GetDirectory(WellKnownDirectory.Root), "externals.*");
|
|
||||||
if (allExternalsDirs.Length > _numberOfOldVersionsToKeep)
|
|
||||||
{
|
|
||||||
// there are more than {_numberOfOldVersionsToKeep} externals.version folder.
|
|
||||||
// delete older externals.version folders.
|
|
||||||
foreach (var oldExternalDir in allExternalsDirs)
|
|
||||||
{
|
|
||||||
if (string.Equals(oldExternalDir, Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"externals"), StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
string.Equals(oldExternalDir, Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"externals.{BuildConstants.RunnerPackage.Version}"), StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
string.Equals(oldExternalDir, Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"externals.{targetVersion}"), StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
// skip for current runner version
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Trace.Info($"Delete runner externals folder's backup at {oldExternalDir}.");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
IOUtil.DeleteDirectory(oldExternalDir, token);
|
|
||||||
}
|
|
||||||
catch (Exception ex) when (!(ex is OperationCanceledException))
|
|
||||||
{
|
|
||||||
Trace.Error(ex);
|
|
||||||
Trace.Info($"Catch exception during delete backup folder {oldExternalDir}, ignore this error try delete the backup folder on next auto-update.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GenerateUpdateScript(bool restartInteractiveRunner, string targetVersion)
|
|
||||||
{
|
|
||||||
int processId = Process.GetCurrentProcess().Id;
|
|
||||||
string updateLog = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Diag), $"SelfUpdate-{DateTime.UtcNow.ToString("yyyyMMdd-HHmmss")}.log");
|
|
||||||
string runnerRoot = HostContext.GetDirectory(WellKnownDirectory.Root);
|
|
||||||
|
|
||||||
#if OS_WINDOWS
|
|
||||||
string templateName = "update.cmd.template";
|
|
||||||
#else
|
|
||||||
string templateName = "update.sh.template";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
string templatePath = Path.Combine(runnerRoot, $"bin.{targetVersion}", templateName);
|
|
||||||
string template = File.ReadAllText(templatePath);
|
|
||||||
|
|
||||||
template = template.Replace("_PROCESS_ID_", processId.ToString());
|
|
||||||
template = template.Replace("_RUNNER_PROCESS_NAME_", $"Runner.Listener{IOUtil.ExeExtension}");
|
|
||||||
template = template.Replace("_ROOT_FOLDER_", runnerRoot);
|
|
||||||
template = template.Replace("_EXIST_RUNNER_VERSION_", BuildConstants.RunnerPackage.Version);
|
|
||||||
template = template.Replace("_DOWNLOAD_RUNNER_VERSION_", targetVersion);
|
|
||||||
template = template.Replace("_UPDATE_LOG_", updateLog);
|
|
||||||
template = template.Replace("_RESTART_INTERACTIVE_RUNNER_", restartInteractiveRunner ? "1" : "0");
|
|
||||||
|
|
||||||
#if OS_WINDOWS
|
|
||||||
string scriptName = "_update.cmd";
|
|
||||||
#else
|
|
||||||
string scriptName = "_update.sh";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
string updateScript = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), scriptName);
|
|
||||||
if (File.Exists(updateScript))
|
|
||||||
{
|
|
||||||
IOUtil.DeleteFile(updateScript);
|
|
||||||
}
|
|
||||||
|
|
||||||
File.WriteAllText(updateScript, template);
|
|
||||||
return updateScript;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,8 +6,6 @@ using System.Linq;
|
|||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using GitHub.Services.Common;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Sdk
|
namespace GitHub.Runner.Sdk
|
||||||
{
|
{
|
||||||
@@ -74,25 +72,6 @@ namespace GitHub.Runner.Sdk
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<string> GetFileContentSha256HashAsync(string path)
|
|
||||||
{
|
|
||||||
if (!File.Exists(path))
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
using (FileStream stream = File.OpenRead(path))
|
|
||||||
{
|
|
||||||
using (SHA256 sha256 = SHA256.Create())
|
|
||||||
{
|
|
||||||
byte[] srcHashBytes = await sha256.ComputeHashAsync(stream);
|
|
||||||
var hash = PrimitiveExtensions.ConvertToHexString(srcHashBytes);
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Delete(string path, CancellationToken cancellationToken)
|
public static void Delete(string path, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
DeleteDirectory(path, cancellationToken);
|
DeleteDirectory(path, cancellationToken);
|
||||||
|
|||||||
@@ -85,35 +85,6 @@ namespace GitHub.Runner.Sdk
|
|||||||
VssCredentials credentials,
|
VssCredentials credentials,
|
||||||
IEnumerable<DelegatingHandler> additionalDelegatingHandler = null,
|
IEnumerable<DelegatingHandler> additionalDelegatingHandler = null,
|
||||||
TimeSpan? timeout = null)
|
TimeSpan? timeout = null)
|
||||||
{
|
|
||||||
RawClientHttpRequestSettings settings = GetHttpRequestSettings(timeout);
|
|
||||||
RawConnection connection = new(serverUri, new RawHttpMessageHandler(credentials.Federated, settings), additionalDelegatingHandler);
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static VssCredentials GetVssCredential(ServiceEndpoint serviceEndpoint)
|
|
||||||
{
|
|
||||||
ArgUtil.NotNull(serviceEndpoint, nameof(serviceEndpoint));
|
|
||||||
ArgUtil.NotNull(serviceEndpoint.Authorization, nameof(serviceEndpoint.Authorization));
|
|
||||||
ArgUtil.NotNullOrEmpty(serviceEndpoint.Authorization.Scheme, nameof(serviceEndpoint.Authorization.Scheme));
|
|
||||||
|
|
||||||
if (serviceEndpoint.Authorization.Parameters.Count == 0)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(serviceEndpoint));
|
|
||||||
}
|
|
||||||
|
|
||||||
VssCredentials credentials = null;
|
|
||||||
string accessToken;
|
|
||||||
if (serviceEndpoint.Authorization.Scheme == EndpointAuthorizationSchemes.OAuth &&
|
|
||||||
serviceEndpoint.Authorization.Parameters.TryGetValue(EndpointAuthorizationParameters.AccessToken, out accessToken))
|
|
||||||
{
|
|
||||||
credentials = new VssCredentials(new VssOAuthAccessTokenCredential(accessToken), CredentialPromptType.DoNotPrompt);
|
|
||||||
}
|
|
||||||
|
|
||||||
return credentials;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RawClientHttpRequestSettings GetHttpRequestSettings(TimeSpan? timeout = null)
|
|
||||||
{
|
{
|
||||||
RawClientHttpRequestSettings settings = RawClientHttpRequestSettings.Default.Clone();
|
RawClientHttpRequestSettings settings = RawClientHttpRequestSettings.Default.Clone();
|
||||||
|
|
||||||
@@ -145,7 +116,30 @@ namespace GitHub.Runner.Sdk
|
|||||||
// settings are applied to an HttpRequestMessage.
|
// settings are applied to an HttpRequestMessage.
|
||||||
settings.AcceptLanguages.Remove(CultureInfo.InvariantCulture);
|
settings.AcceptLanguages.Remove(CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
return settings;
|
RawConnection connection = new(serverUri, new RawHttpMessageHandler(credentials.Federated, settings), additionalDelegatingHandler);
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static VssCredentials GetVssCredential(ServiceEndpoint serviceEndpoint)
|
||||||
|
{
|
||||||
|
ArgUtil.NotNull(serviceEndpoint, nameof(serviceEndpoint));
|
||||||
|
ArgUtil.NotNull(serviceEndpoint.Authorization, nameof(serviceEndpoint.Authorization));
|
||||||
|
ArgUtil.NotNullOrEmpty(serviceEndpoint.Authorization.Scheme, nameof(serviceEndpoint.Authorization.Scheme));
|
||||||
|
|
||||||
|
if (serviceEndpoint.Authorization.Parameters.Count == 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(serviceEndpoint));
|
||||||
|
}
|
||||||
|
|
||||||
|
VssCredentials credentials = null;
|
||||||
|
string accessToken;
|
||||||
|
if (serviceEndpoint.Authorization.Scheme == EndpointAuthorizationSchemes.OAuth &&
|
||||||
|
serviceEndpoint.Authorization.Parameters.TryGetValue(EndpointAuthorizationParameters.AccessToken, out accessToken))
|
||||||
|
{
|
||||||
|
credentials = new VssCredentials(new VssOAuthAccessTokenCredential(accessToken), CredentialPromptType.DoNotPrompt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return credentials;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ using System.Linq;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -763,8 +762,6 @@ namespace GitHub.Runner.Worker
|
|||||||
ArgUtil.NotNull(downloadInfo, nameof(downloadInfo));
|
ArgUtil.NotNull(downloadInfo, nameof(downloadInfo));
|
||||||
ArgUtil.NotNullOrEmpty(downloadInfo.NameWithOwner, nameof(downloadInfo.NameWithOwner));
|
ArgUtil.NotNullOrEmpty(downloadInfo.NameWithOwner, nameof(downloadInfo.NameWithOwner));
|
||||||
ArgUtil.NotNullOrEmpty(downloadInfo.Ref, nameof(downloadInfo.Ref));
|
ArgUtil.NotNullOrEmpty(downloadInfo.Ref, nameof(downloadInfo.Ref));
|
||||||
ArgUtil.NotNullOrEmpty(downloadInfo.Ref, nameof(downloadInfo.ResolvedNameWithOwner));
|
|
||||||
ArgUtil.NotNullOrEmpty(downloadInfo.Ref, nameof(downloadInfo.ResolvedSha));
|
|
||||||
|
|
||||||
string destDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), downloadInfo.NameWithOwner.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar), downloadInfo.Ref);
|
string destDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), downloadInfo.NameWithOwner.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar), downloadInfo.Ref);
|
||||||
string watermarkFile = GetWatermarkFilePath(destDirectory);
|
string watermarkFile = GetWatermarkFilePath(destDirectory);
|
||||||
@@ -781,6 +778,11 @@ namespace GitHub.Runner.Worker
|
|||||||
executionContext.Output($"Download action repository '{downloadInfo.NameWithOwner}@{downloadInfo.Ref}' (SHA:{downloadInfo.ResolvedSha})");
|
executionContext.Output($"Download action repository '{downloadInfo.NameWithOwner}@{downloadInfo.Ref}' (SHA:{downloadInfo.ResolvedSha})");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await DownloadRepositoryActionAsync(executionContext, downloadInfo, destDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, WebApi.ActionDownloadInfo downloadInfo, string destDirectory)
|
||||||
|
{
|
||||||
//download and extract action in a temp folder and rename it on success
|
//download and extract action in a temp folder and rename it on success
|
||||||
string tempDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), "_temp_" + Guid.NewGuid());
|
string tempDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), "_temp_" + Guid.NewGuid());
|
||||||
Directory.CreateDirectory(tempDirectory);
|
Directory.CreateDirectory(tempDirectory);
|
||||||
@@ -793,50 +795,102 @@ namespace GitHub.Runner.Worker
|
|||||||
string link = downloadInfo?.TarballUrl;
|
string link = downloadInfo?.TarballUrl;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
Trace.Info($"Save archive '{link}' into {archiveFile}.");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var useActionArchiveCache = false;
|
int retryCount = 0;
|
||||||
if (executionContext.Global.Variables.GetBoolean("DistributedTask.UseActionArchiveCache") == true)
|
|
||||||
|
// Allow up to 20 * 60s for any action to be downloaded from github graph.
|
||||||
|
int timeoutSeconds = 20 * 60;
|
||||||
|
while (retryCount < 3)
|
||||||
{
|
{
|
||||||
var hasActionArchiveCache = false;
|
using (var actionDownloadTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds)))
|
||||||
var actionArchiveCacheDir = Environment.GetEnvironmentVariable(Constants.Variables.Agent.ActionArchiveCacheDirectory);
|
using (var actionDownloadCancellation = CancellationTokenSource.CreateLinkedTokenSource(actionDownloadTimeout.Token, executionContext.CancellationToken))
|
||||||
if (!string.IsNullOrEmpty(actionArchiveCacheDir) &&
|
|
||||||
Directory.Exists(actionArchiveCacheDir))
|
|
||||||
{
|
{
|
||||||
hasActionArchiveCache = true;
|
try
|
||||||
Trace.Info($"Check if action archive '{downloadInfo.ResolvedNameWithOwner}@{downloadInfo.ResolvedSha}' already exists in cache directory '{actionArchiveCacheDir}'");
|
|
||||||
#if OS_WINDOWS
|
|
||||||
var cacheArchiveFile = Path.Combine(actionArchiveCacheDir, downloadInfo.ResolvedNameWithOwner.Replace(Path.DirectorySeparatorChar, '_').Replace(Path.AltDirectorySeparatorChar, '_'), $"{downloadInfo.ResolvedSha}.zip");
|
|
||||||
#else
|
|
||||||
var cacheArchiveFile = Path.Combine(actionArchiveCacheDir, downloadInfo.ResolvedNameWithOwner.Replace(Path.DirectorySeparatorChar, '_').Replace(Path.AltDirectorySeparatorChar, '_'), $"{downloadInfo.ResolvedSha}.tar.gz");
|
|
||||||
#endif
|
|
||||||
if (File.Exists(cacheArchiveFile))
|
|
||||||
{
|
{
|
||||||
try
|
//open zip stream in async mode
|
||||||
|
using (FileStream fs = new(archiveFile, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: _defaultFileStreamBufferSize, useAsync: true))
|
||||||
|
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
||||||
|
using (var httpClient = new HttpClient(httpClientHandler))
|
||||||
{
|
{
|
||||||
Trace.Info($"Found action archive '{cacheArchiveFile}' in cache directory '{actionArchiveCacheDir}'");
|
httpClient.DefaultRequestHeaders.Authorization = CreateAuthHeader(downloadInfo.Authentication?.Token);
|
||||||
File.Copy(cacheArchiveFile, archiveFile);
|
|
||||||
useActionArchiveCache = true;
|
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
||||||
executionContext.Debug($"Copied action archive '{cacheArchiveFile}' to '{archiveFile}'");
|
using (var response = await httpClient.GetAsync(link))
|
||||||
|
{
|
||||||
|
var requestId = UrlUtil.GetGitHubRequestId(response.Headers);
|
||||||
|
if (!string.IsNullOrEmpty(requestId))
|
||||||
|
{
|
||||||
|
Trace.Info($"Request URL: {link} X-GitHub-Request-Id: {requestId} Http Status: {response.StatusCode}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
using (var result = await response.Content.ReadAsStreamAsync())
|
||||||
|
{
|
||||||
|
await result.CopyToAsync(fs, _defaultCopyBufferSize, actionDownloadCancellation.Token);
|
||||||
|
await fs.FlushAsync(actionDownloadCancellation.Token);
|
||||||
|
|
||||||
|
// download succeed, break out the retry loop.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (response.StatusCode == HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
// It doesn't make sense to retry in this case, so just stop
|
||||||
|
throw new ActionNotFoundException(new Uri(link), requestId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Something else bad happened, let's go to our retry logic
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
}
|
||||||
|
catch (OperationCanceledException) when (executionContext.CancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
Trace.Info("Action download has been cancelled.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException ex) when (!executionContext.CancellationToken.IsCancellationRequested && retryCount >= 2)
|
||||||
|
{
|
||||||
|
Trace.Info($"Action download final retry timeout after {timeoutSeconds} seconds.");
|
||||||
|
throw new TimeoutException($"Action '{link}' download has timed out. Error: {ex.Message}");
|
||||||
|
}
|
||||||
|
catch (ActionNotFoundException)
|
||||||
|
{
|
||||||
|
Trace.Info($"The action at '{link}' does not exist");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (retryCount < 2)
|
||||||
|
{
|
||||||
|
retryCount++;
|
||||||
|
Trace.Error($"Fail to download archive '{link}' -- Attempt: {retryCount}");
|
||||||
|
Trace.Error(ex);
|
||||||
|
if (actionDownloadTimeout.Token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
Trace.Error($"Failed to copy action archive '{cacheArchiveFile}' to '{archiveFile}'. Error: {ex}");
|
// action download didn't finish within timeout
|
||||||
|
executionContext.Warning($"Action '{link}' didn't finish download within {timeoutSeconds} seconds.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
executionContext.Warning($"Failed to download action '{link}'. Error: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
executionContext.Global.JobTelemetry.Add(new JobTelemetry()
|
if (String.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GITHUB_ACTION_DOWNLOAD_NO_BACKOFF")))
|
||||||
{
|
{
|
||||||
Type = JobTelemetryType.General,
|
var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30));
|
||||||
Message = $"Action archive cache usage: {downloadInfo.ResolvedNameWithOwner}@{downloadInfo.ResolvedSha} use cache {useActionArchiveCache} has cache {hasActionArchiveCache}"
|
executionContext.Warning($"Back off {backOff.TotalSeconds} seconds before retry.");
|
||||||
});
|
await Task.Delay(backOff);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!useActionArchiveCache)
|
ArgUtil.NotNullOrEmpty(archiveFile, nameof(archiveFile));
|
||||||
{
|
executionContext.Debug($"Download '{link}' to '{archiveFile}'");
|
||||||
await DownloadRepositoryArchive(executionContext, link, downloadInfo.Authentication?.Token, archiveFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
var stagingDirectory = Path.Combine(tempDirectory, "_staging");
|
var stagingDirectory = Path.Combine(tempDirectory, "_staging");
|
||||||
Directory.CreateDirectory(stagingDirectory);
|
Directory.CreateDirectory(stagingDirectory);
|
||||||
@@ -856,13 +910,11 @@ namespace GitHub.Runner.Worker
|
|||||||
// tar -xzf
|
// tar -xzf
|
||||||
using (var processInvoker = HostContext.CreateService<IProcessInvoker>())
|
using (var processInvoker = HostContext.CreateService<IProcessInvoker>())
|
||||||
{
|
{
|
||||||
var tarOutputs = new List<string>();
|
|
||||||
processInvoker.OutputDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
|
processInvoker.OutputDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(args.Data))
|
if (!string.IsNullOrEmpty(args.Data))
|
||||||
{
|
{
|
||||||
Trace.Info(args.Data);
|
Trace.Info(args.Data);
|
||||||
tarOutputs.Add($"STDOUT: {args.Data}");
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -871,23 +923,13 @@ namespace GitHub.Runner.Worker
|
|||||||
if (!string.IsNullOrEmpty(args.Data))
|
if (!string.IsNullOrEmpty(args.Data))
|
||||||
{
|
{
|
||||||
Trace.Error(args.Data);
|
Trace.Error(args.Data);
|
||||||
tarOutputs.Add($"STDERR: {args.Data}");
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
int exitCode = await processInvoker.ExecuteAsync(stagingDirectory, tar, $"-xzf \"{archiveFile}\"", null, executionContext.CancellationToken);
|
int exitCode = await processInvoker.ExecuteAsync(stagingDirectory, tar, $"-xzf \"{archiveFile}\"", null, executionContext.CancellationToken);
|
||||||
if (exitCode != 0)
|
if (exitCode != 0)
|
||||||
{
|
{
|
||||||
if (executionContext.Global.Variables.GetBoolean("DistributedTask.DetailUntarFailure") == true)
|
throw new InvalidActionArchiveException($"Can't use 'tar -xzf' extract archive file: {archiveFile}. Action being checked out: {downloadInfo.NameWithOwner}@{downloadInfo.Ref}. return code: {exitCode}.");
|
||||||
{
|
|
||||||
var fileInfo = new FileInfo(archiveFile);
|
|
||||||
var sha256hash = await IOUtil.GetFileContentSha256HashAsync(archiveFile);
|
|
||||||
throw new InvalidActionArchiveException($"Can't use 'tar -xzf' extract archive file: {archiveFile} (SHA256 '{sha256hash}', size '{fileInfo.Length}' bytes, tar outputs '{string.Join(' ', tarOutputs)}'). Action being checked out: {downloadInfo.NameWithOwner}@{downloadInfo.Ref}. return code: {exitCode}.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidActionArchiveException($"Can't use 'tar -xzf' extract archive file: {archiveFile}. Action being checked out: {downloadInfo.NameWithOwner}@{downloadInfo.Ref}. return code: {exitCode}.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -905,6 +947,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
|
|
||||||
Trace.Verbose("Create watermark file indicate action download succeed.");
|
Trace.Verbose("Create watermark file indicate action download succeed.");
|
||||||
|
string watermarkFile = GetWatermarkFilePath(destDirectory);
|
||||||
File.WriteAllText(watermarkFile, DateTime.UtcNow.ToString());
|
File.WriteAllText(watermarkFile, DateTime.UtcNow.ToString());
|
||||||
|
|
||||||
executionContext.Debug($"Archive '{archiveFile}' has been unzipped into '{destDirectory}'.");
|
executionContext.Debug($"Archive '{archiveFile}' has been unzipped into '{destDirectory}'.");
|
||||||
@@ -1112,104 +1155,6 @@ namespace GitHub.Runner.Worker
|
|||||||
HostContext.SecretMasker.AddValue(base64EncodingToken);
|
HostContext.SecretMasker.AddValue(base64EncodingToken);
|
||||||
return new AuthenticationHeaderValue("Basic", base64EncodingToken);
|
return new AuthenticationHeaderValue("Basic", base64EncodingToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DownloadRepositoryArchive(IExecutionContext executionContext, string downloadUrl, string downloadAuthToken, string archiveFile)
|
|
||||||
{
|
|
||||||
Trace.Info($"Save archive '{downloadUrl}' into {archiveFile}.");
|
|
||||||
int retryCount = 0;
|
|
||||||
|
|
||||||
// Allow up to 20 * 60s for any action to be downloaded from github graph.
|
|
||||||
int timeoutSeconds = 20 * 60;
|
|
||||||
while (retryCount < 3)
|
|
||||||
{
|
|
||||||
using (var actionDownloadTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds)))
|
|
||||||
using (var actionDownloadCancellation = CancellationTokenSource.CreateLinkedTokenSource(actionDownloadTimeout.Token, executionContext.CancellationToken))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//open zip stream in async mode
|
|
||||||
using (FileStream fs = new(archiveFile, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: _defaultFileStreamBufferSize, useAsync: true))
|
|
||||||
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
|
||||||
using (var httpClient = new HttpClient(httpClientHandler))
|
|
||||||
{
|
|
||||||
httpClient.DefaultRequestHeaders.Authorization = CreateAuthHeader(downloadAuthToken);
|
|
||||||
|
|
||||||
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
|
||||||
using (var response = await httpClient.GetAsync(downloadUrl))
|
|
||||||
{
|
|
||||||
var requestId = UrlUtil.GetGitHubRequestId(response.Headers);
|
|
||||||
if (!string.IsNullOrEmpty(requestId))
|
|
||||||
{
|
|
||||||
Trace.Info($"Request URL: {downloadUrl} X-GitHub-Request-Id: {requestId} Http Status: {response.StatusCode}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
using (var result = await response.Content.ReadAsStreamAsync())
|
|
||||||
{
|
|
||||||
await result.CopyToAsync(fs, _defaultCopyBufferSize, actionDownloadCancellation.Token);
|
|
||||||
await fs.FlushAsync(actionDownloadCancellation.Token);
|
|
||||||
|
|
||||||
// download succeed, break out the retry loop.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (response.StatusCode == HttpStatusCode.NotFound)
|
|
||||||
{
|
|
||||||
// It doesn't make sense to retry in this case, so just stop
|
|
||||||
throw new ActionNotFoundException(new Uri(downloadUrl), requestId);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Something else bad happened, let's go to our retry logic
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException) when (executionContext.CancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
Trace.Info("Action download has been cancelled.");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
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}");
|
|
||||||
}
|
|
||||||
catch (ActionNotFoundException)
|
|
||||||
{
|
|
||||||
Trace.Info($"The action at '{downloadUrl}' does not exist");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (Exception ex) when (retryCount < 2)
|
|
||||||
{
|
|
||||||
retryCount++;
|
|
||||||
Trace.Error($"Fail to download archive '{downloadUrl}' -- Attempt: {retryCount}");
|
|
||||||
Trace.Error(ex);
|
|
||||||
if (actionDownloadTimeout.Token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
// action download didn't finish within timeout
|
|
||||||
executionContext.Warning($"Action '{downloadUrl}' didn't finish download within {timeoutSeconds} seconds.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
executionContext.Warning($"Failed to download action '{downloadUrl}'. Error: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (String.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GITHUB_ACTION_DOWNLOAD_NO_BACKOFF")))
|
|
||||||
{
|
|
||||||
var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30));
|
|
||||||
executionContext.Warning($"Back off {backOff.TotalSeconds} seconds before retry.");
|
|
||||||
await Task.Delay(backOff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ArgUtil.NotNullOrEmpty(archiveFile, nameof(archiveFile));
|
|
||||||
executionContext.Debug($"Download '{downloadUrl}' to '{archiveFile}'");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class Definition
|
public sealed class Definition
|
||||||
|
|||||||
@@ -51,13 +51,6 @@ namespace GitHub.Runner.Worker
|
|||||||
HostContext.UserAgents.Add(new ProductInfoHeaderValue("OrchestrationId", orchestrationId.Value));
|
HostContext.UserAgents.Add(new ProductInfoHeaderValue("OrchestrationId", orchestrationId.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
var jobServerQueueTelemetry = false;
|
|
||||||
if (message.Variables.TryGetValue("DistributedTask.EnableJobServerQueueTelemetry", out VariableValue enableJobServerQueueTelemetry) &&
|
|
||||||
!string.IsNullOrEmpty(enableJobServerQueueTelemetry?.Value))
|
|
||||||
{
|
|
||||||
jobServerQueueTelemetry = StringUtil.ConvertToBoolean(enableJobServerQueueTelemetry.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
ServiceEndpoint systemConnection = message.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
ServiceEndpoint systemConnection = message.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
||||||
if (MessageUtil.IsRunServiceJob(message.MessageType))
|
if (MessageUtil.IsRunServiceJob(message.MessageType))
|
||||||
{
|
{
|
||||||
@@ -79,7 +72,7 @@ namespace GitHub.Runner.Worker
|
|||||||
launchServer.InitializeLaunchClient(new Uri(launchReceiverEndpoint), accessToken);
|
launchServer.InitializeLaunchClient(new Uri(launchReceiverEndpoint), accessToken);
|
||||||
}
|
}
|
||||||
_jobServerQueue = HostContext.GetService<IJobServerQueue>();
|
_jobServerQueue = HostContext.GetService<IJobServerQueue>();
|
||||||
_jobServerQueue.Start(message, resultsServiceOnly: true, enableTelemetry: jobServerQueueTelemetry);
|
_jobServerQueue.Start(message, resultsServiceOnly: true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -101,7 +94,7 @@ namespace GitHub.Runner.Worker
|
|||||||
VssConnection jobConnection = VssUtil.CreateConnection(jobServerUrl, jobServerCredential, delegatingHandlers);
|
VssConnection jobConnection = VssUtil.CreateConnection(jobServerUrl, jobServerCredential, delegatingHandlers);
|
||||||
await jobServer.ConnectAsync(jobConnection);
|
await jobServer.ConnectAsync(jobConnection);
|
||||||
|
|
||||||
_jobServerQueue.Start(message, enableTelemetry: jobServerQueueTelemetry);
|
_jobServerQueue.Start(message);
|
||||||
server = jobServer;
|
server = jobServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,12 +395,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var jobQueueTelemetry = await ShutdownQueue(throwOnFailure: true);
|
await ShutdownQueue(throwOnFailure: true);
|
||||||
// include any job telemetry from the background upload process.
|
|
||||||
if (jobQueueTelemetry.Count > 0)
|
|
||||||
{
|
|
||||||
jobContext.Global.JobTelemetry.AddRange(jobQueueTelemetry);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -509,7 +497,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<IList<JobTelemetry>> ShutdownQueue(bool throwOnFailure)
|
private async Task ShutdownQueue(bool throwOnFailure)
|
||||||
{
|
{
|
||||||
if (_jobServerQueue != null)
|
if (_jobServerQueue != null)
|
||||||
{
|
{
|
||||||
@@ -517,7 +505,6 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
Trace.Info("Shutting down the job server queue.");
|
Trace.Info("Shutting down the job server queue.");
|
||||||
await _jobServerQueue.ShutdownAsync();
|
await _jobServerQueue.ShutdownAsync();
|
||||||
return _jobServerQueue.JobTelemetries;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (!throwOnFailure)
|
catch (Exception ex) when (!throwOnFailure)
|
||||||
{
|
{
|
||||||
@@ -529,8 +516,6 @@ namespace GitHub.Runner.Worker
|
|||||||
_jobServerQueue = null; // Prevent multiple attempts.
|
_jobServerQueue = null; // Prevent multiple attempts.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.Empty<JobTelemetry>();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace GitHub.Services.Common
|
|
||||||
{
|
|
||||||
// Set of classes used to bypass token operations
|
|
||||||
// Results Service and External services follow a different auth model but
|
|
||||||
// we are required to pass in a credentials object to create a RawHttpMessageHandler
|
|
||||||
public class NoOpCredentials : FederatedCredential
|
|
||||||
{
|
|
||||||
public NoOpCredentials(IssuedToken initialToken) : base(initialToken)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override VssCredentialsType CredentialType { get; }
|
|
||||||
protected override IssuedTokenProvider OnCreateTokenProvider(Uri serverUrl, IHttpResponse response)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -109,7 +109,7 @@ namespace GitHub.Services.Common
|
|||||||
lock (m_thisLock)
|
lock (m_thisLock)
|
||||||
{
|
{
|
||||||
// Ensure that we attempt to use the most appropriate authentication mechanism by default.
|
// Ensure that we attempt to use the most appropriate authentication mechanism by default.
|
||||||
if (m_tokenProvider == null && !(this.Credentials is NoOpCredentials))
|
if (m_tokenProvider == null)
|
||||||
{
|
{
|
||||||
m_tokenProvider = this.Credentials.CreateTokenProvider(request.RequestUri, null, null);
|
m_tokenProvider = this.Credentials.CreateTokenProvider(request.RequestUri, null, null);
|
||||||
}
|
}
|
||||||
@@ -121,8 +121,7 @@ namespace GitHub.Services.Common
|
|||||||
HttpResponseMessageWrapper responseWrapper;
|
HttpResponseMessageWrapper responseWrapper;
|
||||||
|
|
||||||
Boolean lastResponseDemandedProxyAuth = false;
|
Boolean lastResponseDemandedProxyAuth = false;
|
||||||
// do not retry if we cannot recreate tokens
|
Int32 retries = m_maxAuthRetries;
|
||||||
Int32 retries = this.Credentials is NoOpCredentials ? 0 : m_maxAuthRetries;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||||
@@ -139,12 +138,8 @@ namespace GitHub.Services.Common
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Let's start with sending a token
|
// Let's start with sending a token
|
||||||
IssuedToken token = null;
|
IssuedToken token = await m_tokenProvider.GetTokenAsync(null, tokenSource.Token).ConfigureAwait(false);
|
||||||
if (m_tokenProvider != null)
|
ApplyToken(request, token, applyICredentialsToWebProxy: lastResponseDemandedProxyAuth);
|
||||||
{
|
|
||||||
token = await m_tokenProvider.GetTokenAsync(null, tokenSource.Token).ConfigureAwait(false);
|
|
||||||
ApplyToken(request, token, applyICredentialsToWebProxy: lastResponseDemandedProxyAuth);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The WinHttpHandler will chunk any content that does not have a computed length which is
|
// The WinHttpHandler will chunk any content that does not have a computed length which is
|
||||||
// not what we want. By loading into a buffer up-front we bypass this behavior and there is
|
// not what we want. By loading into a buffer up-front we bypass this behavior and there is
|
||||||
|
|||||||
@@ -461,9 +461,6 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
long? lastMessageId = null,
|
long? lastMessageId = null,
|
||||||
TaskAgentStatus? status = null,
|
TaskAgentStatus? status = null,
|
||||||
string runnerVersion = null,
|
string runnerVersion = null,
|
||||||
string os = null,
|
|
||||||
string architecture = null,
|
|
||||||
bool? disableUpdate = null,
|
|
||||||
object userState = null,
|
object userState = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
@@ -486,21 +483,6 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
queryParams.Add("runnerVersion", runnerVersion);
|
queryParams.Add("runnerVersion", runnerVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (os != null)
|
|
||||||
{
|
|
||||||
queryParams.Add("os", os);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (architecture != null)
|
|
||||||
{
|
|
||||||
queryParams.Add("architecture", architecture);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (disableUpdate != null)
|
|
||||||
{
|
|
||||||
queryParams.Add("disableUpdate", disableUpdate.Value.ToString().ToLower());
|
|
||||||
}
|
|
||||||
|
|
||||||
return SendAsync<TaskAgentMessage>(
|
return SendAsync<TaskAgentMessage>(
|
||||||
httpMethod,
|
httpMethod,
|
||||||
locationId,
|
locationId,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace GitHub.DistributedTask.WebApi
|
namespace GitHub.DistributedTask.WebApi
|
||||||
{
|
{
|
||||||
// do NOT add new enum since it will break backward compatibility with GHES
|
|
||||||
public enum JobTelemetryType
|
public enum JobTelemetryType
|
||||||
{
|
{
|
||||||
[EnumMember]
|
[EnumMember]
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
|
||||||
@@ -16,32 +15,35 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Name = "target_version")]
|
public RunnerRefreshMessage(
|
||||||
|
ulong runnerId,
|
||||||
|
String targetVersion,
|
||||||
|
int? timeoutInSeconds = null)
|
||||||
|
{
|
||||||
|
this.RunnerId = runnerId;
|
||||||
|
this.TimeoutInSeconds = timeoutInSeconds ?? TimeSpan.FromMinutes(60).Seconds;
|
||||||
|
this.TargetVersion = targetVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataMember]
|
||||||
|
public ulong RunnerId
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataMember]
|
||||||
|
public int TimeoutInSeconds
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataMember]
|
||||||
public String TargetVersion
|
public String TargetVersion
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
private set;
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember(Name = "download_url")]
|
|
||||||
public string DownloadUrl
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember(Name = "sha256_checksum")]
|
|
||||||
public string SHA256Checksum
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember(Name = "os")]
|
|
||||||
public string OS
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,12 +56,39 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<BrokerSession> CreateSessionAsync(
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var requestUri = new Uri(Client.BaseAddress, "session");
|
||||||
|
|
||||||
|
var result = await SendAsync<BrokerSession>(
|
||||||
|
new HttpMethod("POST"),
|
||||||
|
requestUri: requestUri,
|
||||||
|
cancellationToken: cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.IsSuccess)
|
||||||
|
{
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.StatusCode == HttpStatusCode.Forbidden)
|
||||||
|
{
|
||||||
|
throw new AccessDeniedException(result.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.StatusCode == HttpStatusCode.Conflict)
|
||||||
|
{
|
||||||
|
throw new TaskAgentSessionConflictException(result.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception($"Failed to get job message: {result.Error}");
|
||||||
|
}
|
||||||
public async Task<TaskAgentMessage> GetRunnerMessageAsync(
|
public async Task<TaskAgentMessage> GetRunnerMessageAsync(
|
||||||
|
string sessionID,
|
||||||
string runnerVersion,
|
string runnerVersion,
|
||||||
TaskAgentStatus? status,
|
TaskAgentStatus? status,
|
||||||
string os = null,
|
|
||||||
string architecture = null,
|
|
||||||
bool? disableUpdate = null,
|
|
||||||
CancellationToken cancellationToken = default
|
CancellationToken cancellationToken = default
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
@@ -69,6 +96,10 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
|
|
||||||
List<KeyValuePair<string, string>> queryParams = new List<KeyValuePair<string, string>>();
|
List<KeyValuePair<string, string>> queryParams = new List<KeyValuePair<string, string>>();
|
||||||
|
|
||||||
|
if (sessionID != null)
|
||||||
|
{
|
||||||
|
queryParams.Add("sessionID", runnerVersion);
|
||||||
|
}
|
||||||
if (status != null)
|
if (status != null)
|
||||||
{
|
{
|
||||||
queryParams.Add("status", status.Value.ToString());
|
queryParams.Add("status", status.Value.ToString());
|
||||||
@@ -78,21 +109,6 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
queryParams.Add("runnerVersion", runnerVersion);
|
queryParams.Add("runnerVersion", runnerVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (os != null)
|
|
||||||
{
|
|
||||||
queryParams.Add("os", os);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (architecture != null)
|
|
||||||
{
|
|
||||||
queryParams.Add("architecture", architecture);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (disableUpdate != null)
|
|
||||||
{
|
|
||||||
queryParams.Add("disableUpdate", disableUpdate.Value.ToString().ToLower());
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await SendAsync<TaskAgentMessage>(
|
var result = await SendAsync<TaskAgentMessage>(
|
||||||
new HttpMethod("GET"),
|
new HttpMethod("GET"),
|
||||||
requestUri: requestUri,
|
requestUri: requestUri,
|
||||||
|
|||||||
9
src/Sdk/WebApi/WebApi/BrokerSession.cs
Normal file
9
src/Sdk/WebApi/WebApi/BrokerSession.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GitHub.Actions.RunService.WebApi
|
||||||
|
{
|
||||||
|
public sealed class BrokerSession
|
||||||
|
{
|
||||||
|
public string id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -258,38 +258,6 @@ namespace GitHub.Services.Results.Client
|
|||||||
await StepSummaryUploadCompleteAsync(planId, jobId, stepId, fileSize, cancellationToken);
|
await StepSummaryUploadCompleteAsync(planId, jobId, stepId, fileSize, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<HttpResponseMessage> UploadLogFile(string file, bool finalize, bool firstBlock, string sasUrl, string blobStorageType,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
HttpResponseMessage response;
|
|
||||||
if (firstBlock && finalize)
|
|
||||||
{
|
|
||||||
// This is the one and only block, just use a block blob
|
|
||||||
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
|
|
||||||
{
|
|
||||||
response = await UploadBlockFileAsync(sasUrl, blobStorageType, fileStream, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// This is either not the first block, which means it's using appendBlob; or first block and need to wait for additional blocks. Using append blob in either case.
|
|
||||||
// Create the Append blob
|
|
||||||
if (firstBlock)
|
|
||||||
{
|
|
||||||
await CreateAppendFileAsync(sasUrl, blobStorageType, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload content
|
|
||||||
var fileSize = new FileInfo(file).Length;
|
|
||||||
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
|
|
||||||
{
|
|
||||||
response = await UploadAppendFileAsync(sasUrl, blobStorageType, fileStream, finalize, fileSize, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle file upload for step log
|
// Handle file upload for step log
|
||||||
public async Task UploadResultsStepLogAsync(string planId, string jobId, Guid stepId, string file, bool finalize, bool firstBlock, long lineCount, CancellationToken cancellationToken)
|
public async Task UploadResultsStepLogAsync(string planId, string jobId, Guid stepId, string file, bool finalize, bool firstBlock, long lineCount, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -300,7 +268,18 @@ namespace GitHub.Services.Results.Client
|
|||||||
throw new Exception("Failed to get step log upload url");
|
throw new Exception("Failed to get step log upload url");
|
||||||
}
|
}
|
||||||
|
|
||||||
await UploadLogFile(file, finalize, firstBlock, uploadUrlResponse.LogsUrl, uploadUrlResponse.BlobStorageType, cancellationToken);
|
// Create the Append blob
|
||||||
|
if (firstBlock)
|
||||||
|
{
|
||||||
|
await CreateAppendFileAsync(uploadUrlResponse.LogsUrl, uploadUrlResponse.BlobStorageType, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload content
|
||||||
|
var fileSize = new FileInfo(file).Length;
|
||||||
|
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
|
||||||
|
{
|
||||||
|
var response = await UploadAppendFileAsync(uploadUrlResponse.LogsUrl, uploadUrlResponse.BlobStorageType, fileStream, finalize, fileSize, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
// Update metadata
|
// Update metadata
|
||||||
if (finalize)
|
if (finalize)
|
||||||
@@ -320,7 +299,18 @@ namespace GitHub.Services.Results.Client
|
|||||||
throw new Exception("Failed to get job log upload url");
|
throw new Exception("Failed to get job log upload url");
|
||||||
}
|
}
|
||||||
|
|
||||||
await UploadLogFile(file, finalize, firstBlock, uploadUrlResponse.LogsUrl, uploadUrlResponse.BlobStorageType, cancellationToken);
|
// Create the Append blob
|
||||||
|
if (firstBlock)
|
||||||
|
{
|
||||||
|
await CreateAppendFileAsync(uploadUrlResponse.LogsUrl, uploadUrlResponse.BlobStorageType, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload content
|
||||||
|
var fileSize = new FileInfo(file).Length;
|
||||||
|
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
|
||||||
|
{
|
||||||
|
var response = await UploadAppendFileAsync(uploadUrlResponse.LogsUrl, uploadUrlResponse.BlobStorageType, fileStream, finalize, fileSize, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
// Update metadata
|
// Update metadata
|
||||||
if (finalize)
|
if (finalize)
|
||||||
|
|||||||
@@ -172,43 +172,6 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("randomString", "0", "VERB", "FALSELEVEL")]
|
|
||||||
[InlineData("", "-1", "", "INFO")]
|
|
||||||
[InlineData("", "6", "VERB", "FALSELEVEL")]
|
|
||||||
[InlineData("", "99", "VERB", "FALSELEVEL")]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Common")]
|
|
||||||
public void TraceLevel(string trace, string traceLevel, string mustContainTraceLevel, string mustNotContainTraceLevel)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Environment.SetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_TRACE", trace);
|
|
||||||
Environment.SetEnvironmentVariable("ACTIONS_RUNNER_TRACE_LEVEL", traceLevel);
|
|
||||||
|
|
||||||
// Arrange.
|
|
||||||
Setup();
|
|
||||||
_hc.SetDefaultCulture("hu-HU"); // logs [VERB] if traceLevel allows it
|
|
||||||
|
|
||||||
// Assert.
|
|
||||||
var logFile = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"trace_{nameof(HostContextL0)}_{nameof(TraceLevel)}.log");
|
|
||||||
var tempFile = Path.GetTempFileName();
|
|
||||||
File.Delete(tempFile);
|
|
||||||
File.Copy(logFile, tempFile);
|
|
||||||
|
|
||||||
var content = File.ReadAllText(tempFile);
|
|
||||||
Assert.Contains($"{mustContainTraceLevel}", content);
|
|
||||||
Assert.DoesNotContain($"{mustNotContainTraceLevel}", content);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Environment.SetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_TRACE", null);
|
|
||||||
Environment.SetEnvironmentVariable("ACTIONS_RUNNER_TRACE_LEVEL", null);
|
|
||||||
// Cleanup.
|
|
||||||
Teardown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Setup([CallerMemberName] string testName = "")
|
private void Setup([CallerMemberName] string testName = "")
|
||||||
{
|
{
|
||||||
_tokenSource = new CancellationTokenSource();
|
_tokenSource = new CancellationTokenSource();
|
||||||
|
|||||||
73
src/Test/L0/Listener/BrokerMessageListenerL0.cs
Normal file
73
src/Test/L0/Listener/BrokerMessageListenerL0.cs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GitHub.Actions.RunService.WebApi;
|
||||||
|
using GitHub.Runner.Listener;
|
||||||
|
using GitHub.Runner.Listener.Configuration;
|
||||||
|
using GitHub.Services.Common;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Common.Tests.Listener
|
||||||
|
{
|
||||||
|
public sealed class BrokerMessageListenerL0
|
||||||
|
{
|
||||||
|
private readonly RunnerSettings _settings;
|
||||||
|
private readonly Mock<IConfigurationManager> _config;
|
||||||
|
private readonly Mock<IBrokerServer> _brokerServer;
|
||||||
|
private readonly Mock<ICredentialManager> _credMgr;
|
||||||
|
|
||||||
|
public BrokerMessageListenerL0()
|
||||||
|
{
|
||||||
|
_settings = new RunnerSettings { AgentId = 1, AgentName = "myagent", PoolId = 123, PoolName = "default", ServerUrlV2 = "http://myserver", WorkFolder = "_work" };
|
||||||
|
_config = new Mock<IConfigurationManager>();
|
||||||
|
_config.Setup(x => x.LoadSettings()).Returns(_settings);
|
||||||
|
_brokerServer = new Mock<IBrokerServer>();
|
||||||
|
_credMgr = new Mock<ICredentialManager>();
|
||||||
|
_credMgr.Setup(x => x.LoadCredentials()).Returns(new VssCredentials());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async void CreatesSession()
|
||||||
|
{
|
||||||
|
using TestHostContext tc = CreateTestContext();
|
||||||
|
using var tokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
_brokerServer
|
||||||
|
.Setup(
|
||||||
|
x => x.CreateSessionAsync(
|
||||||
|
new Uri(_settings.ServerUrlV2),
|
||||||
|
It.Is<VssCredentials>(y => y != null),
|
||||||
|
tokenSource.Token
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.Returns(
|
||||||
|
Task.FromResult(
|
||||||
|
new BrokerSession { id = "my-phony-session-id" }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
BrokerMessageListener listener = new();
|
||||||
|
listener.Initialize(tc);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
bool result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
Assert.Equal("my-phony-session-id", listener._sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
|
||||||
|
{
|
||||||
|
TestHostContext tc = new(this, testName);
|
||||||
|
tc.SetSingleton<IConfigurationManager>(_config.Object);
|
||||||
|
tc.SetSingleton<IBrokerServer>(_brokerServer.Object);
|
||||||
|
tc.SetSingleton<ICredentialManager>(_credMgr.Object);
|
||||||
|
return tc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -192,8 +192,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
|
|
||||||
_runnerServer
|
_runnerServer
|
||||||
.Setup(x => x.GetAgentMessageAsync(
|
.Setup(x => x.GetAgentMessageAsync(
|
||||||
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
|
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
.Returns(async (Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, string runnerVersion, string os, string architecture, bool disableUpdate, CancellationToken cancellationToken) =>
|
.Returns(async (Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, string runnerVersion, CancellationToken cancellationToken) =>
|
||||||
{
|
{
|
||||||
await Task.Yield();
|
await Task.Yield();
|
||||||
return messages.Dequeue();
|
return messages.Dequeue();
|
||||||
@@ -208,7 +208,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
//Assert
|
//Assert
|
||||||
_runnerServer
|
_runnerServer
|
||||||
.Verify(x => x.GetAgentMessageAsync(
|
.Verify(x => x.GetAgentMessageAsync(
|
||||||
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Exactly(arMessages.Length));
|
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Exactly(arMessages.Length));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +293,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
|
|
||||||
_runnerServer
|
_runnerServer
|
||||||
.Setup(x => x.GetAgentMessageAsync(
|
.Setup(x => x.GetAgentMessageAsync(
|
||||||
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
|
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
.Throws(new TaskAgentAccessTokenExpiredException("test"));
|
.Throws(new TaskAgentAccessTokenExpiredException("test"));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -311,7 +311,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
//Assert
|
//Assert
|
||||||
_runnerServer
|
_runnerServer
|
||||||
.Verify(x => x.GetAgentMessageAsync(
|
.Verify(x => x.GetAgentMessageAsync(
|
||||||
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Once);
|
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
|
||||||
_runnerServer
|
_runnerServer
|
||||||
.Verify(x => x.DeleteAgentSessionAsync(
|
.Verify(x => x.DeleteAgentSessionAsync(
|
||||||
|
|||||||
@@ -1,241 +0,0 @@
|
|||||||
#if !(OS_WINDOWS && ARM64)
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
using GitHub.Runner.Listener;
|
|
||||||
using GitHub.Runner.Sdk;
|
|
||||||
using Moq;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Common.Tests.Listener
|
|
||||||
{
|
|
||||||
public sealed class SelfUpdaterV2L0
|
|
||||||
{
|
|
||||||
private Mock<IRunnerServer> _runnerServer;
|
|
||||||
private Mock<ITerminal> _term;
|
|
||||||
private Mock<IConfigurationStore> _configStore;
|
|
||||||
private Mock<IJobDispatcher> _jobDispatcher;
|
|
||||||
private AgentRefreshMessage _refreshMessage = new(1, "2.999.0");
|
|
||||||
private List<TrimmedPackageMetadata> _trimmedPackages = new();
|
|
||||||
|
|
||||||
#if !OS_WINDOWS
|
|
||||||
private string _packageUrl = null;
|
|
||||||
#else
|
|
||||||
private string _packageUrl = null;
|
|
||||||
#endif
|
|
||||||
public SelfUpdaterV2L0()
|
|
||||||
{
|
|
||||||
_runnerServer = new Mock<IRunnerServer>();
|
|
||||||
_term = new Mock<ITerminal>();
|
|
||||||
_configStore = new Mock<IConfigurationStore>();
|
|
||||||
_jobDispatcher = new Mock<IJobDispatcher>();
|
|
||||||
_configStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1, AgentId = 1 });
|
|
||||||
|
|
||||||
Environment.SetEnvironmentVariable("_GITHUB_ACTION_EXECUTE_UPDATE_SCRIPT", "1");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task FetchLatestRunner()
|
|
||||||
{
|
|
||||||
var latestVersion = "";
|
|
||||||
var httpClientHandler = new HttpClientHandler();
|
|
||||||
httpClientHandler.AllowAutoRedirect = false;
|
|
||||||
using (var client = new HttpClient(httpClientHandler))
|
|
||||||
{
|
|
||||||
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://github.com/actions/runner/releases/latest"));
|
|
||||||
if (response.StatusCode == System.Net.HttpStatusCode.Redirect)
|
|
||||||
{
|
|
||||||
var redirectUrl = response.Headers.Location.ToString();
|
|
||||||
Regex regex = new(@"/runner/releases/tag/v(?<version>\d+\.\d+\.\d+)");
|
|
||||||
var match = regex.Match(redirectUrl);
|
|
||||||
if (match.Success)
|
|
||||||
{
|
|
||||||
latestVersion = match.Groups["version"].Value;
|
|
||||||
|
|
||||||
#if !OS_WINDOWS
|
|
||||||
_packageUrl = $"https://github.com/actions/runner/releases/download/v{latestVersion}/actions-runner-{BuildConstants.RunnerPackage.PackageName}-{latestVersion}.tar.gz";
|
|
||||||
#else
|
|
||||||
_packageUrl = $"https://github.com/actions/runner/releases/download/v{latestVersion}/actions-runner-{BuildConstants.RunnerPackage.PackageName}-{latestVersion}.zip";
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new Exception("The latest runner version could not be determined so a download URL could not be generated for it. Please check the location header of the redirect response of 'https://github.com/actions/runner/releases/latest'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Runner")]
|
|
||||||
public async void TestSelfUpdateAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await FetchLatestRunner();
|
|
||||||
Assert.NotNull(_packageUrl);
|
|
||||||
Assert.NotNull(_trimmedPackages);
|
|
||||||
Environment.SetEnvironmentVariable("RUNNER_L0_OVERRIDEBINDIR", Path.GetFullPath(Path.Combine(TestUtil.GetSrcPath(), "..", "_layout", "bin")));
|
|
||||||
using (var hc = new TestHostContext(this))
|
|
||||||
{
|
|
||||||
hc.GetTrace().Info(_packageUrl);
|
|
||||||
hc.GetTrace().Info(StringUtil.ConvertToJson(_trimmedPackages));
|
|
||||||
|
|
||||||
//Arrange
|
|
||||||
var updater = new Runner.Listener.SelfUpdaterV2();
|
|
||||||
hc.SetSingleton<ITerminal>(_term.Object);
|
|
||||||
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
|
||||||
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
|
||||||
hc.SetSingleton<IHttpClientHandlerFactory>(new HttpClientHandlerFactory());
|
|
||||||
|
|
||||||
var p1 = new ProcessInvokerWrapper();
|
|
||||||
p1.Initialize(hc);
|
|
||||||
var p2 = new ProcessInvokerWrapper();
|
|
||||||
p2.Initialize(hc);
|
|
||||||
var p3 = new ProcessInvokerWrapper();
|
|
||||||
p3.Initialize(hc);
|
|
||||||
hc.EnqueueInstance<IProcessInvoker>(p1);
|
|
||||||
hc.EnqueueInstance<IProcessInvoker>(p2);
|
|
||||||
hc.EnqueueInstance<IProcessInvoker>(p3);
|
|
||||||
updater.Initialize(hc);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var message = new RunnerRefreshMessage()
|
|
||||||
{
|
|
||||||
TargetVersion = "2.999.0",
|
|
||||||
OS = BuildConstants.RunnerPackage.PackageName,
|
|
||||||
DownloadUrl = _packageUrl
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = await updater.SelfUpdate(message, _jobDispatcher.Object, true, hc.RunnerShutdownToken);
|
|
||||||
Assert.True(result);
|
|
||||||
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.999.0")));
|
|
||||||
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.999.0")));
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "bin.2.999.0"), CancellationToken.None);
|
|
||||||
IOUtil.DeleteDirectory(Path.Combine(hc.GetDirectory(WellKnownDirectory.Root), "externals.2.999.0"), CancellationToken.None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Environment.SetEnvironmentVariable("RUNNER_L0_OVERRIDEBINDIR", null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Runner")]
|
|
||||||
public async void TestSelfUpdateAsync_DownloadRetry()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await FetchLatestRunner();
|
|
||||||
Assert.NotNull(_packageUrl);
|
|
||||||
Assert.NotNull(_trimmedPackages);
|
|
||||||
Environment.SetEnvironmentVariable("RUNNER_L0_OVERRIDEBINDIR", Path.GetFullPath(Path.Combine(TestUtil.GetSrcPath(), "..", "_layout", "bin")));
|
|
||||||
using (var hc = new TestHostContext(this))
|
|
||||||
{
|
|
||||||
hc.GetTrace().Info(_packageUrl);
|
|
||||||
hc.GetTrace().Info(StringUtil.ConvertToJson(_trimmedPackages));
|
|
||||||
|
|
||||||
//Arrange
|
|
||||||
var updater = new Runner.Listener.SelfUpdaterV2();
|
|
||||||
hc.SetSingleton<ITerminal>(_term.Object);
|
|
||||||
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
|
||||||
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
|
||||||
hc.SetSingleton<IHttpClientHandlerFactory>(new HttpClientHandlerFactory());
|
|
||||||
|
|
||||||
var p1 = new ProcessInvokerWrapper();
|
|
||||||
p1.Initialize(hc);
|
|
||||||
var p2 = new ProcessInvokerWrapper();
|
|
||||||
p2.Initialize(hc);
|
|
||||||
var p3 = new ProcessInvokerWrapper();
|
|
||||||
p3.Initialize(hc);
|
|
||||||
hc.EnqueueInstance<IProcessInvoker>(p1);
|
|
||||||
hc.EnqueueInstance<IProcessInvoker>(p2);
|
|
||||||
hc.EnqueueInstance<IProcessInvoker>(p3);
|
|
||||||
updater.Initialize(hc);
|
|
||||||
|
|
||||||
var message = new RunnerRefreshMessage()
|
|
||||||
{
|
|
||||||
TargetVersion = "2.999.0",
|
|
||||||
OS = BuildConstants.RunnerPackage.PackageName,
|
|
||||||
DownloadUrl = "https://github.com/actions/runner/notexists"
|
|
||||||
};
|
|
||||||
|
|
||||||
var ex = await Assert.ThrowsAsync<TaskCanceledException>(() => updater.SelfUpdate(message, _jobDispatcher.Object, true, hc.RunnerShutdownToken));
|
|
||||||
Assert.Contains($"failed after {Constants.RunnerDownloadRetryMaxAttempts} download attempts", ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Environment.SetEnvironmentVariable("RUNNER_L0_OVERRIDEBINDIR", null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Runner")]
|
|
||||||
public async void TestSelfUpdateAsync_ValidateHash()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await FetchLatestRunner();
|
|
||||||
Assert.NotNull(_packageUrl);
|
|
||||||
Assert.NotNull(_trimmedPackages);
|
|
||||||
Environment.SetEnvironmentVariable("RUNNER_L0_OVERRIDEBINDIR", Path.GetFullPath(Path.Combine(TestUtil.GetSrcPath(), "..", "_layout", "bin")));
|
|
||||||
using (var hc = new TestHostContext(this))
|
|
||||||
{
|
|
||||||
hc.GetTrace().Info(_packageUrl);
|
|
||||||
hc.GetTrace().Info(StringUtil.ConvertToJson(_trimmedPackages));
|
|
||||||
|
|
||||||
//Arrange
|
|
||||||
var updater = new Runner.Listener.SelfUpdaterV2();
|
|
||||||
hc.SetSingleton<ITerminal>(_term.Object);
|
|
||||||
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
|
||||||
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
|
||||||
hc.SetSingleton<IHttpClientHandlerFactory>(new HttpClientHandlerFactory());
|
|
||||||
|
|
||||||
var p1 = new ProcessInvokerWrapper();
|
|
||||||
p1.Initialize(hc);
|
|
||||||
var p2 = new ProcessInvokerWrapper();
|
|
||||||
p2.Initialize(hc);
|
|
||||||
var p3 = new ProcessInvokerWrapper();
|
|
||||||
p3.Initialize(hc);
|
|
||||||
hc.EnqueueInstance<IProcessInvoker>(p1);
|
|
||||||
hc.EnqueueInstance<IProcessInvoker>(p2);
|
|
||||||
hc.EnqueueInstance<IProcessInvoker>(p3);
|
|
||||||
updater.Initialize(hc);
|
|
||||||
|
|
||||||
var message = new RunnerRefreshMessage()
|
|
||||||
{
|
|
||||||
TargetVersion = "2.999.0",
|
|
||||||
OS = BuildConstants.RunnerPackage.PackageName,
|
|
||||||
DownloadUrl = _packageUrl,
|
|
||||||
SHA256Checksum = "badhash"
|
|
||||||
};
|
|
||||||
|
|
||||||
var ex = await Assert.ThrowsAsync<Exception>(() => updater.SelfUpdate(message, _jobDispatcher.Object, true, hc.RunnerShutdownToken));
|
|
||||||
Assert.Contains("did not match expected Runner Hash", ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Environment.SetEnvironmentVariable("RUNNER_L0_OVERRIDEBINDIR", null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -273,29 +273,5 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
Assert.True(string.Equals(hashResult, File.ReadAllText(externalsHashFile).Trim()), $"Hash mismatch for externals. You might need to update `Misc/contentHash/externals/{BuildConstants.RunnerPackage.PackageName}` or check if `hashFiles.ts` ever changed recently.");
|
Assert.True(string.Equals(hashResult, File.ReadAllText(externalsHashFile).Trim()), $"Hash mismatch for externals. You might need to update `Misc/contentHash/externals/{BuildConstants.RunnerPackage.PackageName}` or check if `hashFiles.ts` ever changed recently.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Common")]
|
|
||||||
public Task RunnerLayoutParts_ContentHashFilesNoNewline()
|
|
||||||
{
|
|
||||||
using (TestHostContext hc = new(this))
|
|
||||||
{
|
|
||||||
Tracing trace = hc.GetTrace();
|
|
||||||
|
|
||||||
var dotnetRuntimeHashFile = Path.Combine(TestUtil.GetSrcPath(), $"Misc/contentHash/dotnetRuntime/{BuildConstants.RunnerPackage.PackageName}");
|
|
||||||
var dotnetRuntimeHash = File.ReadAllText(dotnetRuntimeHashFile);
|
|
||||||
trace.Info($"Current hash: {dotnetRuntimeHash}");
|
|
||||||
|
|
||||||
var externalsHashFile = Path.Combine(TestUtil.GetSrcPath(), $"Misc/contentHash/externals/{BuildConstants.RunnerPackage.PackageName}");
|
|
||||||
var externalsHash = File.ReadAllText(externalsHashFile);
|
|
||||||
trace.Info($"Current hash: {externalsHash}");
|
|
||||||
|
|
||||||
Assert.False(externalsHash.Any(x => char.IsWhiteSpace(x)), $"Found whitespace in externals hash file.");
|
|
||||||
Assert.False(dotnetRuntimeHash.Any(x => char.IsWhiteSpace(x)), $"Found whitespace in dotnet runtime hash file.");
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -293,118 +293,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Worker")]
|
|
||||||
public async void PrepareActions_DownloadActionFromGraph_UseCache()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//Arrange
|
|
||||||
Setup();
|
|
||||||
Directory.CreateDirectory(Path.Combine(_hc.GetDirectory(WellKnownDirectory.Temp), "action_cache"));
|
|
||||||
Directory.CreateDirectory(Path.Combine(_hc.GetDirectory(WellKnownDirectory.Temp), "action_cache", "actions_download-artifact"));
|
|
||||||
Directory.CreateDirectory(Path.Combine(_hc.GetDirectory(WellKnownDirectory.Temp), "actions-download-artifact"));
|
|
||||||
Environment.SetEnvironmentVariable(Constants.Variables.Agent.ActionArchiveCacheDirectory, Path.Combine(_hc.GetDirectory(WellKnownDirectory.Temp), "action_cache"));
|
|
||||||
|
|
||||||
const string Content = @"
|
|
||||||
# Container action
|
|
||||||
name: '1ae80bcb-c1df-4362-bdaa-54f729c60281'
|
|
||||||
description: 'Greet the world and record the time'
|
|
||||||
author: 'GitHub'
|
|
||||||
inputs:
|
|
||||||
greeting: # id of input
|
|
||||||
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
|
||||||
required: true
|
|
||||||
default: 'Hello'
|
|
||||||
entryPoint: # id of input
|
|
||||||
description: 'optional docker entrypoint overwrite.'
|
|
||||||
required: false
|
|
||||||
outputs:
|
|
||||||
time: # id of output
|
|
||||||
description: 'The time we did the greeting'
|
|
||||||
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
|
|
||||||
color: 'green' # optional, decorates the entry in the GitHub Marketplace
|
|
||||||
runs:
|
|
||||||
using: 'node12'
|
|
||||||
main: 'task.js'
|
|
||||||
";
|
|
||||||
await File.WriteAllTextAsync(Path.Combine(_hc.GetDirectory(WellKnownDirectory.Temp), "actions-download-artifact", "action.yml"), Content);
|
|
||||||
|
|
||||||
#if OS_WINDOWS
|
|
||||||
ZipFile.CreateFromDirectory(Path.Combine(_hc.GetDirectory(WellKnownDirectory.Temp), "actions-download-artifact"), Path.Combine(_hc.GetDirectory(WellKnownDirectory.Temp), "action_cache", "actions_download-artifact", "master-sha.zip"), CompressionLevel.Fastest, true);
|
|
||||||
#else
|
|
||||||
string tar = WhichUtil.Which("tar", require: true, trace: _hc.GetTrace());
|
|
||||||
|
|
||||||
// tar -xzf
|
|
||||||
using (var processInvoker = new ProcessInvokerWrapper())
|
|
||||||
{
|
|
||||||
processInvoker.Initialize(_hc);
|
|
||||||
processInvoker.OutputDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(args.Data))
|
|
||||||
{
|
|
||||||
_hc.GetTrace().Info(args.Data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
processInvoker.ErrorDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(args.Data))
|
|
||||||
{
|
|
||||||
_hc.GetTrace().Error(args.Data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
string cwd = Path.GetDirectoryName(Path.Combine(_hc.GetDirectory(WellKnownDirectory.Temp), "actions-download-artifact"));
|
|
||||||
string inputDirectory = Path.GetFileName(Path.Combine(_hc.GetDirectory(WellKnownDirectory.Temp), "actions-download-artifact"));
|
|
||||||
string archiveFile = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Temp), "action_cache", "actions_download-artifact", "master-sha.tar.gz");
|
|
||||||
int exitCode = await processInvoker.ExecuteAsync(_hc.GetDirectory(WellKnownDirectory.Bin), tar, $"-czf \"{archiveFile}\" -C \"{cwd}\" \"{inputDirectory}\"", null, CancellationToken.None);
|
|
||||||
if (exitCode != 0)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException($"Can't use 'tar -czf' to create archive file: {archiveFile}. return code: {exitCode}.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
var actionId = Guid.NewGuid();
|
|
||||||
var actions = new List<Pipelines.ActionStep>
|
|
||||||
{
|
|
||||||
new Pipelines.ActionStep()
|
|
||||||
{
|
|
||||||
Name = "action",
|
|
||||||
Id = actionId,
|
|
||||||
Reference = new Pipelines.RepositoryPathReference()
|
|
||||||
{
|
|
||||||
Name = "actions/download-artifact",
|
|
||||||
Ref = "master",
|
|
||||||
RepositoryType = "GitHub"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
_ec.Object.Global.Variables.Set("DistributedTask.UseActionArchiveCache", bool.TrueString);
|
|
||||||
|
|
||||||
//Act
|
|
||||||
await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
|
||||||
|
|
||||||
//Assert
|
|
||||||
var watermarkFile = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "actions/download-artifact", "master.completed");
|
|
||||||
Assert.True(File.Exists(watermarkFile));
|
|
||||||
|
|
||||||
var actionYamlFile = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "actions/download-artifact", "master", "action.yml");
|
|
||||||
Assert.True(File.Exists(actionYamlFile));
|
|
||||||
|
|
||||||
_hc.GetTrace().Info(File.ReadAllText(actionYamlFile));
|
|
||||||
|
|
||||||
Assert.Contains("1ae80bcb-c1df-4362-bdaa-54f729c60281", File.ReadAllText(actionYamlFile));
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Environment.SetEnvironmentVariable(Constants.Variables.Agent.ActionArchiveCacheDirectory, null);
|
|
||||||
Teardown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
@@ -2384,7 +2272,6 @@ runs:
|
|||||||
_ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
|
_ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
|
||||||
_ec.Object.Global.FileTable = new List<String>();
|
_ec.Object.Global.FileTable = new List<String>();
|
||||||
_ec.Object.Global.Plan = new TaskOrchestrationPlanReference();
|
_ec.Object.Global.Plan = new TaskOrchestrationPlanReference();
|
||||||
_ec.Object.Global.JobTelemetry = new List<JobTelemetry>();
|
|
||||||
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"[{tag}]{message}"); });
|
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"[{tag}]{message}"); });
|
||||||
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<ExecutionContextLogOptions>())).Callback((Issue issue, ExecutionContextLogOptions logOptions) => { _hc.GetTrace().Info($"[{issue.Type}]{logOptions.LogMessageOverride ?? issue.Message}"); });
|
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<ExecutionContextLogOptions>())).Callback((Issue issue, ExecutionContextLogOptions logOptions) => { _hc.GetTrace().Info($"[{issue.Type}]{logOptions.LogMessageOverride ?? issue.Message}"); });
|
||||||
_ec.Setup(x => x.GetGitHubContext("workspace")).Returns(Path.Combine(_workFolder, "actions", "actions"));
|
_ec.Setup(x => x.GetGitHubContext("workspace")).Returns(Path.Combine(_workFolder, "actions", "actions"));
|
||||||
@@ -2407,8 +2294,6 @@ runs:
|
|||||||
{
|
{
|
||||||
NameWithOwner = action.NameWithOwner,
|
NameWithOwner = action.NameWithOwner,
|
||||||
Ref = action.Ref,
|
Ref = action.Ref,
|
||||||
ResolvedNameWithOwner = action.NameWithOwner,
|
|
||||||
ResolvedSha = $"{action.Ref}-sha",
|
|
||||||
TarballUrl = $"https://api.github.com/repos/{action.NameWithOwner}/tarball/{action.Ref}",
|
TarballUrl = $"https://api.github.com/repos/{action.NameWithOwner}/tarball/{action.Ref}",
|
||||||
ZipballUrl = $"https://api.github.com/repos/{action.NameWithOwner}/zipball/{action.Ref}",
|
ZipballUrl = $"https://api.github.com/repos/{action.NameWithOwner}/zipball/{action.Ref}",
|
||||||
};
|
};
|
||||||
@@ -2428,8 +2313,6 @@ runs:
|
|||||||
{
|
{
|
||||||
NameWithOwner = action.NameWithOwner,
|
NameWithOwner = action.NameWithOwner,
|
||||||
Ref = action.Ref,
|
Ref = action.Ref,
|
||||||
ResolvedNameWithOwner = action.NameWithOwner,
|
|
||||||
ResolvedSha = $"{action.Ref}-sha",
|
|
||||||
TarballUrl = $"https://api.github.com/repos/{action.NameWithOwner}/tarball/{action.Ref}",
|
TarballUrl = $"https://api.github.com/repos/{action.NameWithOwner}/tarball/{action.Ref}",
|
||||||
ZipballUrl = $"https://api.github.com/repos/{action.NameWithOwner}/zipball/{action.Ref}",
|
ZipballUrl = $"https://api.github.com/repos/{action.NameWithOwner}/zipball/{action.Ref}",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
|
|||||||
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
||||||
PACKAGE_TRIMS_DIR="$SCRIPT_DIR/../_package_trims"
|
PACKAGE_TRIMS_DIR="$SCRIPT_DIR/../_package_trims"
|
||||||
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
||||||
DOTNETSDK_VERSION="6.0.415"
|
DOTNETSDK_VERSION="6.0.414"
|
||||||
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
||||||
RUNNER_VERSION=$(cat runnerversion)
|
RUNNER_VERSION=$(cat runnerversion)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"sdk": {
|
"sdk": {
|
||||||
"version": "6.0.415"
|
"version": "6.0.414"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.311.0
|
2.309.0
|
||||||
|
|||||||
Reference in New Issue
Block a user