mirror of
https://github.com/actions/runner.git
synced 2025-12-11 12:57:05 +00:00
Compare commits
1 Commits
all-the-ru
...
v2.300.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3e56206cf |
@@ -5,10 +5,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.405"
|
"version": "6.0.300"
|
||||||
},
|
|
||||||
"ghcr.io/devcontainers/features/node:1": {
|
|
||||||
"version": "16"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"customizations": {
|
"customizations": {
|
||||||
|
|||||||
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,8 +1,5 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: 🛑 Request a feature in the runner application
|
|
||||||
url: https://github.com/orgs/community/discussions/categories/actions-and-packages
|
|
||||||
about: If you have feature requests for GitHub Actions, please use the Actions and Packages section on the Github Product Feedback page.
|
|
||||||
- name: ✅ Support for GitHub Actions
|
- name: ✅ Support for GitHub Actions
|
||||||
url: https://github.community/c/code-to-cloud/52
|
url: https://github.community/c/code-to-cloud/52
|
||||||
about: If you have questions about GitHub Actions or need support writing workflows, please ask in the GitHub Community Support forum.
|
about: If you have questions about GitHub Actions or need support writing workflows, please ask in the GitHub Community Support forum.
|
||||||
|
|||||||
32
.github/ISSUE_TEMPLATE/enhancement_request.md
vendored
Normal file
32
.github/ISSUE_TEMPLATE/enhancement_request.md
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
name: 🛑 Request a feature in the runner application
|
||||||
|
about: If you have feature requests for GitHub Actions, please use the "feedback and suggestions for GitHub Actions" link below.
|
||||||
|
title: ''
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
👋 You're opening a request for an enhancement in the GitHub Actions **runner application**.
|
||||||
|
|
||||||
|
🛑 Please stop if you're not certain that the feature you want is in the runner application - if you have a suggestion for improving GitHub Actions, please see the [GitHub Actions Feedback](https://github.com/github/feedback/discussions/categories/actions-and-packages-feedback) discussion forum which is actively monitored. Using the forum ensures that we route your problem to the correct team. 😃
|
||||||
|
|
||||||
|
Some additional useful links:
|
||||||
|
* If you have found a security issue [please submit it here](https://hackerone.com/github)
|
||||||
|
* If you have questions or issues with the service, writing workflows or actions, then please [visit the GitHub Community Forum's Actions Board](https://github.community/t5/GitHub-Actions/bd-p/actions)
|
||||||
|
* If you are having an issue or have a question about GitHub Actions then please [contact customer support](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-github-actions#contacting-support)
|
||||||
|
|
||||||
|
If you have a feature request that is relevant to this repository, the runner, then please include the information below:
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Describe the enhancement**
|
||||||
|
A clear and concise description of what the features or enhancement you need.
|
||||||
|
|
||||||
|
**Code Snippet**
|
||||||
|
If applicable, add a code snippet.
|
||||||
|
|
||||||
|
**Additional information**
|
||||||
|
Add any other context about the feature here.
|
||||||
|
|
||||||
|
NOTE: if the feature request has been agreed upon then the assignee will create an ADR. See docs/adrs/README.md
|
||||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v1
|
||||||
# Override language selection by uncommenting this and choosing your languages
|
# Override language selection by uncommenting this and choosing your languages
|
||||||
# with:
|
# with:
|
||||||
# languages: go, javascript, csharp, python, cpp, java
|
# languages: go, javascript, csharp, python, cpp, java
|
||||||
@@ -38,4 +38,4 @@ jobs:
|
|||||||
working-directory: src
|
working-directory: src
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v1
|
||||||
|
|||||||
1
.github/workflows/lint.yml
vendored
1
.github/workflows/lint.yml
vendored
@@ -18,6 +18,7 @@ jobs:
|
|||||||
uses: github/super-linter@v4
|
uses: github/super-linter@v4
|
||||||
env:
|
env:
|
||||||
DEFAULT_BRANCH: ${{ github.base_ref }}
|
DEFAULT_BRANCH: ${{ github.base_ref }}
|
||||||
|
DISABLE_ERRORS: true
|
||||||
EDITORCONFIG_FILE_NAME: .editorconfig
|
EDITORCONFIG_FILE_NAME: .editorconfig
|
||||||
LINTER_RULES_PATH: /src/
|
LINTER_RULES_PATH: /src/
|
||||||
VALIDATE_ALL_CODEBASE: false
|
VALIDATE_ALL_CODEBASE: false
|
||||||
|
|||||||
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
@@ -131,7 +131,7 @@ jobs:
|
|||||||
file=$(ls)
|
file=$(ls)
|
||||||
sha=$(sha256sum $file | awk '{ print $1 }')
|
sha=$(sha256sum $file | awk '{ print $1 }')
|
||||||
echo "Computed sha256: $sha for $file"
|
echo "Computed sha256: $sha for $file"
|
||||||
echo "${{matrix.runtime}}-sha256=$sha" >> $GITHUB_OUTPUT
|
echo "::set-output name=${{matrix.runtime}}-sha256::$sha"
|
||||||
shell: bash
|
shell: bash
|
||||||
id: sha
|
id: sha
|
||||||
name: Compute SHA256
|
name: Compute SHA256
|
||||||
@@ -140,8 +140,8 @@ jobs:
|
|||||||
file=$(ls)
|
file=$(ls)
|
||||||
sha=$(sha256sum $file | awk '{ print $1 }')
|
sha=$(sha256sum $file | awk '{ print $1 }')
|
||||||
echo "Computed sha256: $sha for $file"
|
echo "Computed sha256: $sha for $file"
|
||||||
echo "${{matrix.runtime}}-sha256=$sha" >> $GITHUB_OUTPUT
|
echo "::set-output name=${{matrix.runtime}}-sha256::$sha"
|
||||||
echo "sha256=$sha" >> $GITHUB_OUTPUT
|
echo "::set-output name=sha256::$sha"
|
||||||
shell: bash
|
shell: bash
|
||||||
id: sha_noexternals
|
id: sha_noexternals
|
||||||
name: Compute SHA256
|
name: Compute SHA256
|
||||||
@@ -150,8 +150,8 @@ jobs:
|
|||||||
file=$(ls)
|
file=$(ls)
|
||||||
sha=$(sha256sum $file | awk '{ print $1 }')
|
sha=$(sha256sum $file | awk '{ print $1 }')
|
||||||
echo "Computed sha256: $sha for $file"
|
echo "Computed sha256: $sha for $file"
|
||||||
echo "${{matrix.runtime}}-sha256=$sha" >> $GITHUB_OUTPUT
|
echo "::set-output name=${{matrix.runtime}}-sha256::$sha"
|
||||||
echo "sha256=$sha" >> $GITHUB_OUTPUT
|
echo "::set-output name=sha256::$sha"
|
||||||
shell: bash
|
shell: bash
|
||||||
id: sha_noruntime
|
id: sha_noruntime
|
||||||
name: Compute SHA256
|
name: Compute SHA256
|
||||||
@@ -160,8 +160,8 @@ jobs:
|
|||||||
file=$(ls)
|
file=$(ls)
|
||||||
sha=$(sha256sum $file | awk '{ print $1 }')
|
sha=$(sha256sum $file | awk '{ print $1 }')
|
||||||
echo "Computed sha256: $sha for $file"
|
echo "Computed sha256: $sha for $file"
|
||||||
echo "${{matrix.runtime}}-sha256=$sha" >> $GITHUB_OUTPUT
|
echo "::set-output name=${{matrix.runtime}}-sha256::$sha"
|
||||||
echo "sha256=$sha" >> $GITHUB_OUTPUT
|
echo "::set-output name=sha256::$sha"
|
||||||
shell: bash
|
shell: bash
|
||||||
id: sha_noruntime_noexternals
|
id: sha_noruntime_noexternals
|
||||||
name: Compute SHA256
|
name: Compute SHA256
|
||||||
|
|||||||
@@ -158,11 +158,3 @@ cat (Runner/Worker)_TIMESTAMP.log # view your log file
|
|||||||
|
|
||||||
We use the .NET Foundation and CoreCLR style guidelines [located here](
|
We use the .NET Foundation and CoreCLR style guidelines [located here](
|
||||||
https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/coding-style.md)
|
https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/coding-style.md)
|
||||||
|
|
||||||
### Format C# Code
|
|
||||||
|
|
||||||
To format both staged and unstaged .cs files
|
|
||||||
```
|
|
||||||
cd ./src
|
|
||||||
./dev.(cmd|sh) format
|
|
||||||
```
|
|
||||||
@@ -35,7 +35,7 @@ All the configs below can be found in `.vscode/launch.json`.
|
|||||||
If you launch `Run` or `Run [build]`, it starts a process called `Runner.Listener`.
|
If you launch `Run` or `Run [build]`, it starts a process called `Runner.Listener`.
|
||||||
This process will receive any job queued on this repository if the job runs on matching labels (e.g `runs-on: self-hosted`).
|
This process will receive any job queued on this repository if the job runs on matching labels (e.g `runs-on: self-hosted`).
|
||||||
Once a job is received, a `Runner.Listener` starts a new process of `Runner.Worker`.
|
Once a job is received, a `Runner.Listener` starts a new process of `Runner.Worker`.
|
||||||
Since this is a different process, you can't use the same debugger session debug it.
|
Since this is a diferent process, you can't use the same debugger session debug it.
|
||||||
Instead, a parallel debugging session has to be started, using a different launch config.
|
Instead, a parallel debugging session has to be started, using a different launch config.
|
||||||
Luckily, VS Code supports multiple parallel debugging sessions.
|
Luckily, VS Code supports multiple parallel debugging sessions.
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ FROM mcr.microsoft.com/dotnet/runtime-deps:6.0 as build
|
|||||||
|
|
||||||
ARG RUNNER_VERSION
|
ARG RUNNER_VERSION
|
||||||
ARG RUNNER_ARCH="x64"
|
ARG RUNNER_ARCH="x64"
|
||||||
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.3.1
|
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.1.3
|
||||||
ARG DOCKER_VERSION=20.10.23
|
|
||||||
|
|
||||||
RUN apt update -y && apt install curl unzip -y
|
RUN apt update -y && apt install curl unzip -y
|
||||||
|
|
||||||
@@ -16,34 +15,10 @@ RUN curl -f -L -o runner-container-hooks.zip https://github.com/actions/runner-c
|
|||||||
&& unzip ./runner-container-hooks.zip -d ./k8s \
|
&& unzip ./runner-container-hooks.zip -d ./k8s \
|
||||||
&& rm runner-container-hooks.zip
|
&& rm runner-container-hooks.zip
|
||||||
|
|
||||||
RUN export DOCKER_ARCH=x86_64 \
|
|
||||||
&& if [ "$RUNNER_ARCH" = "arm64" ]; then export DOCKER_ARCH=aarch64 ; fi \
|
|
||||||
&& curl -fLo docker.tgz https://download.docker.com/linux/static/stable/${DOCKER_ARCH}/docker-${DOCKER_VERSION}.tgz \
|
|
||||||
&& tar zxvf docker.tgz \
|
|
||||||
&& rm -rf docker.tgz
|
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/runtime-deps:6.0
|
FROM mcr.microsoft.com/dotnet/runtime-deps:6.0
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV RUNNER_ALLOW_RUNASROOT=1
|
||||||
ENV RUNNER_MANUALLY_TRAP_SIG=1
|
ENV RUNNER_MANUALLY_TRAP_SIG=1
|
||||||
ENV ACTIONS_RUNNER_PRINT_LOG_TO_STDOUT=1
|
|
||||||
|
|
||||||
RUN apt-get update -y \
|
WORKDIR /actions-runner
|
||||||
&& apt-get install -y --no-install-recommends \
|
COPY --from=build /actions-runner .
|
||||||
sudo \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
RUN adduser --disabled-password --gecos "" --uid 1001 runner \
|
|
||||||
&& groupadd docker --gid 123 \
|
|
||||||
&& usermod -aG sudo runner \
|
|
||||||
&& usermod -aG docker runner \
|
|
||||||
&& echo "%sudo ALL=(ALL:ALL) NOPASSWD:ALL" > /etc/sudoers \
|
|
||||||
&& echo "Defaults env_keep += \"DEBIAN_FRONTEND\"" >> /etc/sudoers
|
|
||||||
|
|
||||||
WORKDIR /home/runner
|
|
||||||
|
|
||||||
COPY --chown=runner:docker --from=build /actions-runner .
|
|
||||||
|
|
||||||
RUN install -o root -g root -m 755 docker/* /usr/bin/ && rm -rf docker
|
|
||||||
|
|
||||||
USER runner
|
|
||||||
|
|||||||
@@ -1,21 +1,17 @@
|
|||||||
## Features
|
## Features
|
||||||
- Support matrix context in output keys (#2477)
|
- Expose github.actor_id, github.workflow_ref & github.workflow_sha as environment variable (#2249)
|
||||||
- Add update certificates to `./run.sh` if `RUNNER_UPDATE_CA_CERTS` env is set (#2471)
|
- Added worker and listener logs to stdout (#2291, #2307)
|
||||||
- Bypass all proxies for all hosts if `no_proxy='*'` is set (#2395)
|
|
||||||
- Change runner image to make user/folder align with `ubuntu-latest` hosted runner. (#2469)
|
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
- Exit on runner version deprecation error (#2299)
|
- Made github.action_status output lowercase to be consistent with job.status' output (#1944)
|
||||||
- Runner service exit after consecutive re-try exits (#2426)
|
|
||||||
|
|
||||||
## Misc
|
## Misc
|
||||||
- Replace deprecated command with environment file (#2429)
|
- Added small size runner image for ARC (#2250)
|
||||||
- Make requests to `Run` service to renew job request (#2461)
|
- Small change to Node.js 12 deprecation message (#2262)
|
||||||
- Add job/step log upload to Result service (#2447, #2439)
|
- Added the option to use the --replace argument to the create-latest-svc.sh (#2273)
|
||||||
|
- Made runner_name optional defaulting to hostname in delete.sh script (#1871)
|
||||||
_Note: Actions Runner follows a progressive release policy, so the latest release might not be available to your enterprise, organization, or repository yet.
|
- Return exit code when MANUALLY_TRAP_SIG is exported (#2285)
|
||||||
To confirm which version of the Actions Runner you should expect, please view the download instructions for your enterprise, organization, or repository.
|
- Use results for uploading step summaries (#2301)
|
||||||
See https://docs.github.com/en/enterprise-cloud@latest/actions/hosting-your-own-runners/adding-self-hosted-runners_
|
|
||||||
|
|
||||||
## Windows x64
|
## Windows x64
|
||||||
We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows.
|
We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows.
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<Update to ./src/runnerversion when creating release>
|
2.300.0
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
39f2a931565d6a10e695ac8ed14bb9dcbb568151410349b32dbf9c27bae29602
|
1d709d93e5d3c6c6c656a61aa6c1781050224788a05b0e6ecc4c3c0408bdf89c
|
||||||
@@ -1 +1 @@
|
|||||||
29ffb303537d8ba674fbebc7729292c21c4ebd17b3198f91ed593ef4cbbb67b5
|
b92a47cfeaad02255b1f7a377060651b73ae5e5db22a188dbbcb4183ab03a03d
|
||||||
@@ -1 +1 @@
|
|||||||
de6868a836fa3cb9e5ddddbc079da1c25e819aa2d2fc193cc9931c353687c57c
|
68a9a8ef0843a8bb74241894f6f63fd76241a82295c5337d3cc7a940a314c78e
|
||||||
@@ -1 +1 @@
|
|||||||
339d3e1a5fd28450c0fe6cb820cc7aae291f0f9e2d153ac34e1f7b080e35d30e
|
02c7126ff4d63ee2a0ae390c81434c125630522aadf35903bbeebb1a99d8af99
|
||||||
@@ -1 +1 @@
|
|||||||
dcb7f606c1d7d290381e5020ee73e7f16dcbd2f20ac9b431362ccbb5120d449c
|
c9d5a542f8d765168855a89e83ae0a8970d00869041c4f9a766651c04c72b212
|
||||||
@@ -1 +1 @@
|
|||||||
1bbcb0e9a2cf4be4b1fce77458de139b70ac58efcbb415a6db028b9373ae1673
|
39d0683f0f115a211cb10c473e9574c16549a19d4e9a6c637ded3d7022bf809f
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
44cd25f3c104d0abb44d262397a80e0b2c4f206465c5d899a22eec043dac0fb3
|
d94f2fbaf210297162bc9f3add819d73682c3aa6899e321c3872412b924d5504
|
||||||
2
src/Misc/contentHash/externals/linux-arm
vendored
2
src/Misc/contentHash/externals/linux-arm
vendored
@@ -1 +1 @@
|
|||||||
3807dcbf947e840c33535fb466b096d76bf09e5c0254af8fc8cbbb24c6388222
|
6ed30a2c1ee403a610d63e82bb230b9ba846a9c25cec9e4ea8672fb6ed4e1a51
|
||||||
2
src/Misc/contentHash/externals/linux-arm64
vendored
2
src/Misc/contentHash/externals/linux-arm64
vendored
@@ -1 +1 @@
|
|||||||
ee01eee80cd8a460a4b9780ee13fdd20f25c59e754b4ccd99df55fbba2a85634
|
711c30c51ec52c9b7a9a2eb399d6ab2ab5ee1dc72de11879f2f36f919f163d78
|
||||||
2
src/Misc/contentHash/externals/linux-x64
vendored
2
src/Misc/contentHash/externals/linux-x64
vendored
@@ -1 +1 @@
|
|||||||
a9fb9c14e24e79aec97d4da197dd7bfc6364297d6fce573afb2df48cc9a931f8
|
a49479ca4b4988a06c097e8d22c51fd08a11c13f40807366236213d0e008cf6a
|
||||||
2
src/Misc/contentHash/externals/osx-arm64
vendored
2
src/Misc/contentHash/externals/osx-arm64
vendored
@@ -1 +1 @@
|
|||||||
a4e0e8fc62eba0967a39c7d693dcd0aeb8b2bed0765f9c38df80d42884f65341
|
cc4708962a80325de0baa5ae8484e0cb9ae976ac6a4178c1c0d448b8c52bd7f7
|
||||||
2
src/Misc/contentHash/externals/osx-x64
vendored
2
src/Misc/contentHash/externals/osx-x64
vendored
@@ -1 +1 @@
|
|||||||
17ac17fbe785b3d6fa2868d8d17185ebfe0c90b4b0ddf6b67eac70e42bcd989b
|
8e97df75230b843462a9b4c578ccec604ee4b4a1066120c85b04374317fa372b
|
||||||
2
src/Misc/contentHash/externals/win-arm64
vendored
2
src/Misc/contentHash/externals/win-arm64
vendored
@@ -1 +1 @@
|
|||||||
89f24657a550f1e818b0e9975e5b80edcf4dd22b7d4bccbb9e48e37f45d30fb1
|
e5dace2d41cc0682d096dcce4970079ad48ec7107e46195970eecfdb3df2acef
|
||||||
|
|||||||
2
src/Misc/contentHash/externals/win-x64
vendored
2
src/Misc/contentHash/externals/win-x64
vendored
@@ -1 +1 @@
|
|||||||
24fd131b5dce33ef16038b771407bc0507da8682a72fb3b7780607235f76db0b
|
f75a671e5a188c76680739689aa75331a2c09d483dce9c80023518c48fd67a18
|
||||||
43
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
43
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
@@ -14,7 +14,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^12.7.12",
|
"@types/node": "^12.7.12",
|
||||||
"@typescript-eslint/parser": "^5.15.0",
|
"@typescript-eslint/parser": "^5.15.0",
|
||||||
"@vercel/ncc": "^0.36.0",
|
"@zeit/ncc": "^0.20.5",
|
||||||
"eslint": "^8.11.0",
|
"eslint": "^8.11.0",
|
||||||
"eslint-plugin-github": "^4.3.5",
|
"eslint-plugin-github": "^4.3.5",
|
||||||
"prettier": "^1.19.1",
|
"prettier": "^1.19.1",
|
||||||
@@ -346,10 +346,11 @@
|
|||||||
"url": "https://opencollective.com/typescript-eslint"
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vercel/ncc": {
|
"node_modules/@zeit/ncc": {
|
||||||
"version": "0.36.0",
|
"version": "0.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.36.0.tgz",
|
"resolved": "https://registry.npmjs.org/@zeit/ncc/-/ncc-0.20.5.tgz",
|
||||||
"integrity": "sha512-/ZTUJ/ZkRt694k7KJNimgmHjtQcRuVwsST2Z6XfYveQIuBbHR+EqkTc1jfgPkQmMyk/vtpxo3nVxe8CNuau86A==",
|
"integrity": "sha512-XU6uzwvv95DqxciQx+aOLhbyBx/13ky+RK1y88Age9Du3BlA4mMPCy13BGjayOrrumOzlq1XV3SD/BWiZENXlw==",
|
||||||
|
"deprecated": "@zeit/ncc is no longer maintained. Please use @vercel/ncc instead.",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"ncc": "dist/ncc/cli.js"
|
"ncc": "dist/ncc/cli.js"
|
||||||
@@ -1721,9 +1722,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/json5": {
|
"node_modules/json5": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimist": "^1.2.0"
|
"minimist": "^1.2.0"
|
||||||
@@ -1823,9 +1824,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
},
|
},
|
||||||
@@ -2746,10 +2747,10 @@
|
|||||||
"eslint-visitor-keys": "^3.0.0"
|
"eslint-visitor-keys": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vercel/ncc": {
|
"@zeit/ncc": {
|
||||||
"version": "0.36.0",
|
"version": "0.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.36.0.tgz",
|
"resolved": "https://registry.npmjs.org/@zeit/ncc/-/ncc-0.20.5.tgz",
|
||||||
"integrity": "sha512-/ZTUJ/ZkRt694k7KJNimgmHjtQcRuVwsST2Z6XfYveQIuBbHR+EqkTc1jfgPkQmMyk/vtpxo3nVxe8CNuau86A==",
|
"integrity": "sha512-XU6uzwvv95DqxciQx+aOLhbyBx/13ky+RK1y88Age9Du3BlA4mMPCy13BGjayOrrumOzlq1XV3SD/BWiZENXlw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"acorn": {
|
"acorn": {
|
||||||
@@ -3755,9 +3756,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"json5": {
|
"json5": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimist": "^1.2.0"
|
"minimist": "^1.2.0"
|
||||||
@@ -3839,9 +3840,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimatch": {
|
"minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^12.7.12",
|
"@types/node": "^12.7.12",
|
||||||
"@typescript-eslint/parser": "^5.15.0",
|
"@typescript-eslint/parser": "^5.15.0",
|
||||||
"@vercel/ncc": "^0.36.0",
|
"@zeit/ncc": "^0.20.5",
|
||||||
"eslint": "^8.11.0",
|
"eslint": "^8.11.0",
|
||||||
"eslint-plugin-github": "^4.3.5",
|
"eslint-plugin-github": "^4.3.5",
|
||||||
"prettier": "^1.19.1",
|
"prettier": "^1.19.1",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ PRECACHE=$2
|
|||||||
NODE_URL=https://nodejs.org/dist
|
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
|
||||||
NODE12_VERSION="12.22.7"
|
NODE12_VERSION="12.22.7"
|
||||||
NODE16_VERSION="16.16.0"
|
NODE16_VERSION="16.13.0"
|
||||||
|
|
||||||
get_abs_path() {
|
get_abs_path() {
|
||||||
# exploits the fact that pwd will print abs path when no args
|
# exploits the fact that pwd will print abs path when no args
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ if (exitServiceAfterNFailures <= 0) {
|
|||||||
exitServiceAfterNFailures = NaN;
|
exitServiceAfterNFailures = NaN;
|
||||||
}
|
}
|
||||||
|
|
||||||
var unknownFailureRetryCount = 0;
|
var consecutiveFailureCount = 0;
|
||||||
var retriableFailureRetryCount = 0;
|
|
||||||
|
|
||||||
var gracefulShutdown = function () {
|
var gracefulShutdown = function () {
|
||||||
console.log("Shutting down runner listener");
|
console.log("Shutting down runner listener");
|
||||||
@@ -63,8 +62,7 @@ var runService = function () {
|
|||||||
|
|
||||||
listener.stdout.on("data", (data) => {
|
listener.stdout.on("data", (data) => {
|
||||||
if (data.toString("utf8").includes("Listening for Jobs")) {
|
if (data.toString("utf8").includes("Listening for Jobs")) {
|
||||||
unknownFailureRetryCount = 0;
|
consecutiveFailureCount = 0;
|
||||||
retriableFailureRetryCount = 0;
|
|
||||||
}
|
}
|
||||||
process.stdout.write(data.toString("utf8"));
|
process.stdout.write(data.toString("utf8"));
|
||||||
});
|
});
|
||||||
@@ -94,38 +92,24 @@ var runService = function () {
|
|||||||
console.log(
|
console.log(
|
||||||
"Runner listener exit with retryable error, re-launch runner in 5 seconds."
|
"Runner listener exit with retryable error, re-launch runner in 5 seconds."
|
||||||
);
|
);
|
||||||
unknownFailureRetryCount = 0;
|
consecutiveFailureCount = 0;
|
||||||
retriableFailureRetryCount++;
|
|
||||||
if (retriableFailureRetryCount >= 10) {
|
|
||||||
console.error(
|
|
||||||
"Stopping the runner after 10 consecutive re-tryable failures"
|
|
||||||
);
|
|
||||||
stopping = true;
|
|
||||||
}
|
|
||||||
} else if (code === 3 || code === 4) {
|
} else if (code === 3 || code === 4) {
|
||||||
console.log(
|
console.log(
|
||||||
"Runner listener exit because of updating, re-launch runner in 5 seconds."
|
"Runner listener exit because of updating, re-launch runner in 5 seconds."
|
||||||
);
|
);
|
||||||
unknownFailureRetryCount = 0;
|
consecutiveFailureCount = 0;
|
||||||
retriableFailureRetryCount++;
|
|
||||||
if (retriableFailureRetryCount >= 10) {
|
|
||||||
console.error(
|
|
||||||
"Stopping the runner after 10 consecutive re-tryable failures"
|
|
||||||
);
|
|
||||||
stopping = true;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
var messagePrefix = "Runner listener exit with undefined return code";
|
var messagePrefix = "Runner listener exit with undefined return code";
|
||||||
unknownFailureRetryCount++;
|
consecutiveFailureCount++;
|
||||||
retriableFailureRetryCount = 0;
|
|
||||||
if (
|
if (
|
||||||
!isNaN(exitServiceAfterNFailures) &&
|
!isNaN(exitServiceAfterNFailures) &&
|
||||||
unknownFailureRetryCount >= exitServiceAfterNFailures
|
consecutiveFailureCount >= exitServiceAfterNFailures
|
||||||
) {
|
) {
|
||||||
console.error(
|
console.error(
|
||||||
`${messagePrefix}, exiting service after ${unknownFailureRetryCount} consecutive failures`
|
`${messagePrefix}, exiting service after ${consecutiveFailureCount} consecutive failures`
|
||||||
);
|
);
|
||||||
stopping = true
|
gracefulShutdown();
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
console.log(`${messagePrefix}, re-launch runner in 5 seconds.`);
|
console.log(`${messagePrefix}, re-launch runner in 5 seconds.`);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -18,20 +18,6 @@ while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symli
|
|||||||
done
|
done
|
||||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||||
|
|
||||||
# Wait for docker to start
|
|
||||||
if [ ! -z "$RUNNER_WAIT_FOR_DOCKER_IN_SECONDS" ]; then
|
|
||||||
if [ "$RUNNER_WAIT_FOR_DOCKER_IN_SECONDS" -gt 0 ]; then
|
|
||||||
echo "Waiting for docker to be ready."
|
|
||||||
for i in $(seq "$RUNNER_WAIT_FOR_DOCKER_IN_SECONDS"); do
|
|
||||||
if docker ps > /dev/null 2>&1; then
|
|
||||||
echo "Docker is ready."
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
"$DIR"/safe_sleep.sh 1
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
updateFile="update.finished"
|
updateFile="update.finished"
|
||||||
"$DIR"/bin/Runner.Listener run $*
|
"$DIR"/bin/Runner.Listener run $*
|
||||||
|
|
||||||
|
|||||||
@@ -53,33 +53,6 @@ runWithManualTrap() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCerts() {
|
|
||||||
local sudo_prefix=""
|
|
||||||
local user_id=`id -u`
|
|
||||||
|
|
||||||
if [ $user_id -ne 0 ]; then
|
|
||||||
if [[ ! -x "$(command -v sudo)" ]]; then
|
|
||||||
echo "Warning: failed to update certificate store: sudo is required but not found"
|
|
||||||
return 1
|
|
||||||
else
|
|
||||||
sudo_prefix="sudo"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -x "$(command -v update-ca-certificates)" ]]; then
|
|
||||||
eval $sudo_prefix "update-ca-certificates"
|
|
||||||
elif [[ -x "$(command -v update-ca-trust)" ]]; then
|
|
||||||
eval $sudo_prefix "update-ca-trust"
|
|
||||||
else
|
|
||||||
echo "Warning: failed to update certificate store: update-ca-certificates or update-ca-trust not found. This can happen if you're using a different runner base image."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
if [[ ! -z "$RUNNER_UPDATE_CA_CERTS" ]]; then
|
|
||||||
updateCerts
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z "$RUNNER_MANUALLY_TRAP_SIG" ]]; then
|
if [[ -z "$RUNNER_MANUALLY_TRAP_SIG" ]]; then
|
||||||
run $*
|
run $*
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ Microsoft.Win32.Registry.dll
|
|||||||
mscordaccore.dll
|
mscordaccore.dll
|
||||||
mscordaccore_amd64_amd64_6.0.522.21309.dll
|
mscordaccore_amd64_amd64_6.0.522.21309.dll
|
||||||
mscordaccore_arm64_arm64_6.0.522.21309.dll
|
mscordaccore_arm64_arm64_6.0.522.21309.dll
|
||||||
mscordaccore_amd64_amd64_6.0.1322.58009.dll
|
|
||||||
mscordbi.dll
|
mscordbi.dll
|
||||||
mscorlib.dll
|
mscorlib.dll
|
||||||
mscorrc.debug.dll
|
mscorrc.debug.dll
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using GitHub.Actions.RunService.WebApi;
|
|
||||||
using GitHub.DistributedTask.Pipelines;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
using GitHub.Runner.Sdk;
|
|
||||||
using GitHub.Services.Common;
|
|
||||||
using Sdk.RSWebApi.Contracts;
|
|
||||||
using Sdk.WebApi.WebApi.RawClient;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Common
|
|
||||||
{
|
|
||||||
[ServiceLocator(Default = typeof(BrokerServer))]
|
|
||||||
public interface IBrokerServer : IRunnerService
|
|
||||||
{
|
|
||||||
Task ConnectAsync(Uri serverUrl, VssCredentials credentials);
|
|
||||||
|
|
||||||
Task<TaskAgentMessage> GetRunnerMessageAsync(CancellationToken token, TaskAgentStatus status, string version);
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class BrokerServer : RunnerService, IBrokerServer
|
|
||||||
{
|
|
||||||
private bool _hasConnection;
|
|
||||||
private Uri _brokerUri;
|
|
||||||
private RawConnection _connection;
|
|
||||||
private BrokerHttpClient _brokerHttpClient;
|
|
||||||
|
|
||||||
public async Task ConnectAsync(Uri serverUri, VssCredentials credentials)
|
|
||||||
{
|
|
||||||
_brokerUri = serverUri;
|
|
||||||
|
|
||||||
_connection = VssUtil.CreateRawConnection(serverUri, credentials);
|
|
||||||
_brokerHttpClient = await _connection.GetClientAsync<BrokerHttpClient>();
|
|
||||||
_hasConnection = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CheckConnection()
|
|
||||||
{
|
|
||||||
if (!_hasConnection)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"SetConnection");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<TaskAgentMessage> GetRunnerMessageAsync(CancellationToken cancellationToken, TaskAgentStatus status, string version)
|
|
||||||
{
|
|
||||||
CheckConnection();
|
|
||||||
var jobMessage = RetryRequest<TaskAgentMessage>(
|
|
||||||
async () => await _brokerHttpClient.GetRunnerMessageAsync(version, status, cancellationToken), cancellationToken);
|
|
||||||
|
|
||||||
return jobMessage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -50,12 +50,6 @@ namespace GitHub.Runner.Common
|
|||||||
[DataMember(EmitDefaultValue = false)]
|
[DataMember(EmitDefaultValue = false)]
|
||||||
public string MonitorSocketAddress { get; set; }
|
public string MonitorSocketAddress { get; set; }
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public bool UseV2Flow { get; set; }
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
public string ServerUrlV2 { get; set; }
|
|
||||||
|
|
||||||
[IgnoreDataMember]
|
[IgnoreDataMember]
|
||||||
public bool IsHostedServer
|
public bool IsHostedServer
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ namespace GitHub.Runner.Common
|
|||||||
public static class Args
|
public static class Args
|
||||||
{
|
{
|
||||||
public static readonly string Auth = "auth";
|
public static readonly string Auth = "auth";
|
||||||
|
public static readonly string JitConfig = "jitconfig";
|
||||||
public static readonly string Labels = "labels";
|
public static readonly string Labels = "labels";
|
||||||
public static readonly string MonitorSocketAddress = "monitorsocketaddress";
|
public static readonly string MonitorSocketAddress = "monitorsocketaddress";
|
||||||
public static readonly string Name = "name";
|
public static readonly string Name = "name";
|
||||||
@@ -104,13 +105,11 @@ namespace GitHub.Runner.Common
|
|||||||
public static readonly string Token = "token";
|
public static readonly string Token = "token";
|
||||||
public static readonly string PAT = "pat";
|
public static readonly string PAT = "pat";
|
||||||
public static readonly string WindowsLogonPassword = "windowslogonpassword";
|
public static readonly string WindowsLogonPassword = "windowslogonpassword";
|
||||||
public static readonly string JitConfig = "jitconfig";
|
|
||||||
public static string[] Secrets => new[]
|
public static string[] Secrets => new[]
|
||||||
{
|
{
|
||||||
PAT,
|
PAT,
|
||||||
Token,
|
Token,
|
||||||
WindowsLogonPassword,
|
WindowsLogonPassword,
|
||||||
JitConfig,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +130,6 @@ namespace GitHub.Runner.Common
|
|||||||
public static readonly string Ephemeral = "ephemeral";
|
public static readonly string Ephemeral = "ephemeral";
|
||||||
public static readonly string GenerateServiceConfig = "generateServiceConfig";
|
public static readonly string GenerateServiceConfig = "generateServiceConfig";
|
||||||
public static readonly string Help = "help";
|
public static readonly string Help = "help";
|
||||||
public static readonly string Local = "local";
|
|
||||||
public static readonly string Replace = "replace";
|
public static readonly string Replace = "replace";
|
||||||
public static readonly string DisableUpdate = "disableupdate";
|
public static readonly string DisableUpdate = "disableupdate";
|
||||||
public static readonly string Once = "once"; // Keep this around since customers still relies on it
|
public static readonly string Once = "once"; // Keep this around since customers still relies on it
|
||||||
@@ -159,11 +157,9 @@ namespace GitHub.Runner.Common
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static readonly string InternalTelemetryIssueDataKey = "_internal_telemetry";
|
public static readonly string InternalTelemetryIssueDataKey = "_internal_telemetry";
|
||||||
public static readonly Guid TelemetryRecordId = new Guid("11111111-1111-1111-1111-111111111111");
|
|
||||||
public static readonly string WorkerCrash = "WORKER_CRASH";
|
public static readonly string WorkerCrash = "WORKER_CRASH";
|
||||||
public static readonly string LowDiskSpace = "LOW_DISK_SPACE";
|
public static readonly string LowDiskSpace = "LOW_DISK_SPACE";
|
||||||
public static readonly string UnsupportedCommand = "UNSUPPORTED_COMMAND";
|
public static readonly string UnsupportedCommand = "UNSUPPORTED_COMMAND";
|
||||||
public static readonly string ResultsUploadFailure = "RESULTS_UPLOAD_FAILURE";
|
|
||||||
public static readonly string UnsupportedCommandMessage = "The `{0}` command is deprecated and will be disabled soon. Please upgrade to using Environment Files. For more information see: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/";
|
public static readonly string UnsupportedCommandMessage = "The `{0}` command is deprecated and will be disabled soon. Please upgrade to using Environment Files. For more information see: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/";
|
||||||
public static readonly string UnsupportedCommandMessageDisabled = "The `{0}` command is disabled. Please upgrade to using Environment Files or opt into unsecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_COMMANDS` environment variable to `true`. For more information see: https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/";
|
public static readonly string UnsupportedCommandMessageDisabled = "The `{0}` command is disabled. Please upgrade to using Environment Files or opt into unsecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_COMMANDS` environment variable to `true`. For more information see: https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/";
|
||||||
public static readonly string UnsupportedStopCommandTokenDisabled = "You cannot use a endToken that is an empty string, the string 'pause-logging', or another workflow command. For more information see: https://docs.github.com/actions/learn-github-actions/workflow-commands-for-github-actions#example-stopping-and-starting-workflow-commands or opt into insecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_STOPCOMMAND_TOKENS` environment variable to `true`.";
|
public static readonly string UnsupportedStopCommandTokenDisabled = "You cannot use a endToken that is an empty string, the string 'pause-logging', or another workflow command. For more information see: https://docs.github.com/actions/learn-github-actions/workflow-commands-for-github-actions#example-stopping-and-starting-workflow-commands or opt into insecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_STOPCOMMAND_TOKENS` environment variable to `true`.";
|
||||||
|
|||||||
@@ -226,20 +226,6 @@ namespace GitHub.Runner.Common
|
|||||||
}
|
}
|
||||||
|
|
||||||
_userAgents.Add(new ProductInfoHeaderValue("CommitSHA", BuildConstants.Source.CommitHash));
|
_userAgents.Add(new ProductInfoHeaderValue("CommitSHA", BuildConstants.Source.CommitHash));
|
||||||
|
|
||||||
var extraUserAgent = Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_EXTRA_USER_AGENT");
|
|
||||||
if (!string.IsNullOrEmpty(extraUserAgent))
|
|
||||||
{
|
|
||||||
var extraUserAgentSplit = extraUserAgent.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
if (extraUserAgentSplit.Length != 2)
|
|
||||||
{
|
|
||||||
_trace.Error($"GITHUB_ACTIONS_RUNNER_EXTRA_USER_AGENT is not in the format of 'name/version'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var extraUserAgentHeader = new ProductInfoHeaderValue(extraUserAgentSplit[0], extraUserAgentSplit[1]);
|
|
||||||
_trace.Info($"Adding extra user agent '{extraUserAgentHeader}' to all HTTP requests.");
|
|
||||||
_userAgents.Add(extraUserAgentHeader);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetDirectory(WellKnownDirectory directory)
|
public string GetDirectory(WellKnownDirectory directory)
|
||||||
|
|||||||
@@ -30,9 +30,7 @@ namespace GitHub.Runner.Common
|
|||||||
Task<TaskLog> AppendLogContentAsync(Guid scopeIdentifier, string hubName, Guid planId, int logId, Stream uploadStream, CancellationToken cancellationToken);
|
Task<TaskLog> AppendLogContentAsync(Guid scopeIdentifier, string hubName, Guid planId, int logId, Stream uploadStream, CancellationToken cancellationToken);
|
||||||
Task AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, long? startLine, CancellationToken cancellationToken);
|
Task AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, long? startLine, CancellationToken cancellationToken);
|
||||||
Task<TaskAttachment> CreateAttachmentAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, String type, String name, Stream uploadStream, CancellationToken cancellationToken);
|
Task<TaskAttachment> CreateAttachmentAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, String type, String name, Stream uploadStream, CancellationToken cancellationToken);
|
||||||
Task CreateStepSummaryAsync(string planId, string jobId, Guid stepId, string file, CancellationToken cancellationToken);
|
Task CreateStepSymmaryAsync(string planId, string jobId, string stepId, string file, CancellationToken cancellationToken);
|
||||||
Task CreateResultsStepLogAsync(string planId, string jobId, Guid stepId, string file, bool finalize, bool firstBlock, long lineCount, CancellationToken cancellationToken);
|
|
||||||
Task CreateResultsJobLogAsync(string planId, string jobId, string file, bool finalize, bool firstBlock, long lineCount, CancellationToken cancellationToken);
|
|
||||||
Task<TaskLog> CreateLogAsync(Guid scopeIdentifier, string hubName, Guid planId, TaskLog log, CancellationToken cancellationToken);
|
Task<TaskLog> CreateLogAsync(Guid scopeIdentifier, string hubName, Guid planId, TaskLog log, CancellationToken cancellationToken);
|
||||||
Task<Timeline> CreateTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken);
|
Task<Timeline> CreateTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken);
|
||||||
Task<List<TimelineRecord>> UpdateTimelineRecordsAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, IEnumerable<TimelineRecord> records, CancellationToken cancellationToken);
|
Task<List<TimelineRecord>> UpdateTimelineRecordsAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, IEnumerable<TimelineRecord> records, CancellationToken cancellationToken);
|
||||||
@@ -318,7 +316,7 @@ namespace GitHub.Runner.Common
|
|||||||
return _taskClient.CreateAttachmentAsync(scopeIdentifier, hubName, planId, timelineId, timelineRecordId, type, name, uploadStream, cancellationToken: cancellationToken);
|
return _taskClient.CreateAttachmentAsync(scopeIdentifier, hubName, planId, timelineId, timelineRecordId, type, name, uploadStream, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task CreateStepSummaryAsync(string planId, string jobId, Guid stepId, string file, CancellationToken cancellationToken)
|
public Task CreateStepSymmaryAsync(string planId, string jobId, string stepId, string file, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_resultsClient != null)
|
if (_resultsClient != null)
|
||||||
{
|
{
|
||||||
@@ -327,23 +325,6 @@ namespace GitHub.Runner.Common
|
|||||||
throw new InvalidOperationException("Results client is not initialized.");
|
throw new InvalidOperationException("Results client is not initialized.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task CreateResultsStepLogAsync(string planId, string jobId, Guid stepId, string file, bool finalize, bool firstBlock, long lineCount, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (_resultsClient != null)
|
|
||||||
{
|
|
||||||
return _resultsClient.UploadResultsStepLogAsync(planId, jobId, stepId, file, finalize, firstBlock, lineCount, cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
throw new InvalidOperationException("Results client is not initialized.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task CreateResultsJobLogAsync(string planId, string jobId, string file, bool finalize, bool firstBlock, long lineCount, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (_resultsClient != null)
|
|
||||||
{
|
|
||||||
return _resultsClient.UploadResultsJobLogAsync(planId, jobId, file, finalize, firstBlock, lineCount, cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
throw new InvalidOperationException("Results client is not initialized.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<TaskLog> CreateLogAsync(Guid scopeIdentifier, string hubName, Guid planId, TaskLog log, CancellationToken cancellationToken)
|
public Task<TaskLog> CreateLogAsync(Guid scopeIdentifier, string hubName, Guid planId, TaskLog log, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ namespace GitHub.Runner.Common
|
|||||||
void Start(Pipelines.AgentJobRequestMessage jobRequest);
|
void Start(Pipelines.AgentJobRequestMessage jobRequest);
|
||||||
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 QueueSummaryUpload(Guid timelineId, Guid timelineRecordId, string stepId, string name, string path, bool deleteSource);
|
||||||
void QueueTimelineRecordUpdate(Guid timelineId, TimelineRecord timelineRecord);
|
void QueueTimelineRecordUpdate(Guid timelineId, TimelineRecord timelineRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ namespace GitHub.Runner.Common
|
|||||||
private static readonly TimeSpan _delayForWebConsoleLineDequeue = TimeSpan.FromMilliseconds(500);
|
private static readonly TimeSpan _delayForWebConsoleLineDequeue = TimeSpan.FromMilliseconds(500);
|
||||||
private static readonly TimeSpan _delayForTimelineUpdateDequeue = TimeSpan.FromMilliseconds(500);
|
private static readonly TimeSpan _delayForTimelineUpdateDequeue = TimeSpan.FromMilliseconds(500);
|
||||||
private static readonly TimeSpan _delayForFileUploadDequeue = TimeSpan.FromMilliseconds(1000);
|
private static readonly TimeSpan _delayForFileUploadDequeue = TimeSpan.FromMilliseconds(1000);
|
||||||
private static readonly TimeSpan _delayForResultsUploadDequeue = TimeSpan.FromMilliseconds(1000);
|
private static readonly TimeSpan _delayForSummaryUploadDequeue = TimeSpan.FromMilliseconds(1000);
|
||||||
|
|
||||||
// Job message information
|
// Job message information
|
||||||
private Guid _scopeIdentifier;
|
private Guid _scopeIdentifier;
|
||||||
@@ -46,7 +46,7 @@ namespace GitHub.Runner.Common
|
|||||||
// queue for file upload (log file or attachment)
|
// queue for file upload (log file or attachment)
|
||||||
private readonly ConcurrentQueue<UploadFileInfo> _fileUploadQueue = new();
|
private readonly ConcurrentQueue<UploadFileInfo> _fileUploadQueue = new();
|
||||||
|
|
||||||
private readonly ConcurrentQueue<ResultsUploadFileInfo> _resultsFileUploadQueue = new();
|
private readonly ConcurrentQueue<SummaryUploadFileInfo> _summaryFileUploadQueue = new();
|
||||||
|
|
||||||
// queue for timeline or timeline record update (one queue per timeline)
|
// queue for timeline or timeline record update (one queue per timeline)
|
||||||
private readonly ConcurrentDictionary<Guid, ConcurrentQueue<TimelineRecord>> _timelineUpdateQueue = new();
|
private readonly ConcurrentDictionary<Guid, ConcurrentQueue<TimelineRecord>> _timelineUpdateQueue = new();
|
||||||
@@ -60,7 +60,7 @@ namespace GitHub.Runner.Common
|
|||||||
// Task for each queue's dequeue process
|
// Task for each queue's dequeue process
|
||||||
private Task _webConsoleLineDequeueTask;
|
private Task _webConsoleLineDequeueTask;
|
||||||
private Task _fileUploadDequeueTask;
|
private Task _fileUploadDequeueTask;
|
||||||
private Task _resultsUploadDequeueTask;
|
private Task _summaryUploadDequeueTask;
|
||||||
private Task _timelineUpdateDequeueTask;
|
private Task _timelineUpdateDequeueTask;
|
||||||
|
|
||||||
// common
|
// common
|
||||||
@@ -84,9 +84,6 @@ namespace GitHub.Runner.Common
|
|||||||
private bool _webConsoleLineAggressiveDequeue = true;
|
private bool _webConsoleLineAggressiveDequeue = true;
|
||||||
private bool _firstConsoleOutputs = true;
|
private bool _firstConsoleOutputs = true;
|
||||||
|
|
||||||
private bool _resultsClientInitiated = false;
|
|
||||||
private delegate Task ResultsFileUploadHandler(ResultsUploadFileInfo file);
|
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
public override void Initialize(IHostContext hostContext)
|
||||||
{
|
{
|
||||||
base.Initialize(hostContext);
|
base.Initialize(hostContext);
|
||||||
@@ -112,9 +109,9 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
Trace.Info("Initializing results client");
|
Trace.Info("Initializing results client");
|
||||||
_jobServer.InitializeResultsClient(new Uri(resultsReceiverEndpoint), accessToken);
|
_jobServer.InitializeResultsClient(new Uri(resultsReceiverEndpoint), accessToken);
|
||||||
_resultsClientInitiated = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (_queueInProcess)
|
if (_queueInProcess)
|
||||||
{
|
{
|
||||||
Trace.Info("No-opt, all queue process tasks are running.");
|
Trace.Info("No-opt, all queue process tasks are running.");
|
||||||
@@ -143,12 +140,12 @@ namespace GitHub.Runner.Common
|
|||||||
_fileUploadDequeueTask = ProcessFilesUploadQueueAsync();
|
_fileUploadDequeueTask = ProcessFilesUploadQueueAsync();
|
||||||
|
|
||||||
Trace.Info("Start results file upload queue.");
|
Trace.Info("Start results file upload queue.");
|
||||||
_resultsUploadDequeueTask = ProcessResultsUploadQueueAsync();
|
_summaryUploadDequeueTask = ProcessSummaryUploadQueueAsync();
|
||||||
|
|
||||||
Trace.Info("Start process timeline update queue.");
|
Trace.Info("Start process timeline update queue.");
|
||||||
_timelineUpdateDequeueTask = ProcessTimelinesUpdateQueueAsync();
|
_timelineUpdateDequeueTask = ProcessTimelinesUpdateQueueAsync();
|
||||||
|
|
||||||
_allDequeueTasks = new Task[] { _webConsoleLineDequeueTask, _fileUploadDequeueTask, _timelineUpdateDequeueTask, _resultsUploadDequeueTask };
|
_allDequeueTasks = new Task[] { _webConsoleLineDequeueTask, _fileUploadDequeueTask, _timelineUpdateDequeueTask, _summaryUploadDequeueTask };
|
||||||
_queueInProcess = true;
|
_queueInProcess = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,9 +176,9 @@ namespace GitHub.Runner.Common
|
|||||||
await ProcessFilesUploadQueueAsync(runOnce: true);
|
await ProcessFilesUploadQueueAsync(runOnce: true);
|
||||||
Trace.Info("File upload queue drained.");
|
Trace.Info("File upload queue drained.");
|
||||||
|
|
||||||
Trace.Verbose("Draining results upload queue.");
|
Trace.Verbose("Draining results summary upload queue.");
|
||||||
await ProcessResultsUploadQueueAsync(runOnce: true);
|
await ProcessSummaryUploadQueueAsync(runOnce: true);
|
||||||
Trace.Info("Results upload queue drained.");
|
Trace.Info("Results summary upload queue drained.");
|
||||||
|
|
||||||
// ProcessTimelinesUpdateQueueAsync() will throw exception during shutdown
|
// ProcessTimelinesUpdateQueueAsync() will throw exception during shutdown
|
||||||
// if there is any timeline records that failed to update contains output variabls.
|
// if there is any timeline records that failed to update contains output variabls.
|
||||||
@@ -233,43 +230,26 @@ namespace GitHub.Runner.Common
|
|||||||
_fileUploadQueue.Enqueue(newFile);
|
_fileUploadQueue.Enqueue(newFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void QueueResultsUpload(Guid timelineRecordId, string name, string path, string type, bool deleteSource, bool finalize, bool firstBlock, long totalLines)
|
public void QueueSummaryUpload(Guid timelineId, Guid timelineRecordId, string stepId, string name, string path, bool deleteSource)
|
||||||
{
|
{
|
||||||
if (!_resultsClientInitiated)
|
ArgUtil.NotEmpty(timelineId, nameof(timelineId));
|
||||||
{
|
ArgUtil.NotEmpty(timelineRecordId, nameof(timelineRecordId));
|
||||||
Trace.Verbose("Skipping results upload");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (deleteSource)
|
|
||||||
{
|
|
||||||
File.Delete(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Trace.Info("Catch exception during delete skipped results upload file.");
|
|
||||||
Trace.Error(ex);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// all parameter not null, file path exist.
|
// all parameter not null, file path exist.
|
||||||
var newFile = new ResultsUploadFileInfo()
|
var newFile = new SummaryUploadFileInfo()
|
||||||
{
|
{
|
||||||
|
TimelineId = timelineId,
|
||||||
|
TimelineRecordId = timelineRecordId,
|
||||||
Name = name,
|
Name = name,
|
||||||
Path = path,
|
Path = path,
|
||||||
Type = type,
|
|
||||||
PlanId = _planId.ToString(),
|
PlanId = _planId.ToString(),
|
||||||
JobId = _jobTimelineRecordId.ToString(),
|
JobId = _jobTimelineRecordId.ToString(),
|
||||||
RecordId = timelineRecordId,
|
StepId = stepId,
|
||||||
DeleteSource = deleteSource,
|
DeleteSource = deleteSource
|
||||||
Finalize = finalize,
|
|
||||||
FirstBlock = firstBlock,
|
|
||||||
TotalLines = totalLines,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Trace.Verbose("Enqueue results file upload queue: file '{0}' attach to job {1} step {2}", newFile.Path, _jobTimelineRecordId, timelineRecordId);
|
Trace.Verbose("Enqueue results file upload queue: file '{0}' attach to record {1}", newFile.Path, timelineRecordId);
|
||||||
_resultsFileUploadQueue.Enqueue(newFile);
|
_summaryFileUploadQueue.Enqueue(newFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void QueueTimelineRecordUpdate(Guid timelineId, TimelineRecord timelineRecord)
|
public void QueueTimelineRecordUpdate(Guid timelineId, TimelineRecord timelineRecord)
|
||||||
@@ -462,18 +442,18 @@ namespace GitHub.Runner.Common
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProcessResultsUploadQueueAsync(bool runOnce = false)
|
private async Task ProcessSummaryUploadQueueAsync(bool runOnce = false)
|
||||||
{
|
{
|
||||||
Trace.Info("Starting results-based upload queue...");
|
Trace.Info("Starting results-based upload queue...");
|
||||||
|
|
||||||
while (!_jobCompletionSource.Task.IsCompleted || runOnce)
|
while (!_jobCompletionSource.Task.IsCompleted || runOnce)
|
||||||
{
|
{
|
||||||
List<ResultsUploadFileInfo> filesToUpload = new();
|
List<SummaryUploadFileInfo> filesToUpload = new();
|
||||||
ResultsUploadFileInfo dequeueFile;
|
SummaryUploadFileInfo dequeueFile;
|
||||||
while (_resultsFileUploadQueue.TryDequeue(out dequeueFile))
|
while (_summaryFileUploadQueue.TryDequeue(out dequeueFile))
|
||||||
{
|
{
|
||||||
filesToUpload.Add(dequeueFile);
|
filesToUpload.Add(dequeueFile);
|
||||||
// process at most 10 file uploads.
|
// process at most 10 file upload.
|
||||||
if (!runOnce && filesToUpload.Count > 10)
|
if (!runOnce && filesToUpload.Count > 10)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
@@ -484,7 +464,7 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
if (runOnce)
|
if (runOnce)
|
||||||
{
|
{
|
||||||
Trace.Info($"Uploading {filesToUpload.Count} file(s) in one shot through results service.");
|
Trace.Info($"Uploading {filesToUpload.Count} summary files in one shot through results service.");
|
||||||
}
|
}
|
||||||
|
|
||||||
int errorCount = 0;
|
int errorCount = 0;
|
||||||
@@ -492,43 +472,17 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (String.Equals(file.Type, ChecksAttachmentType.StepSummary, StringComparison.OrdinalIgnoreCase))
|
await UploadSummaryFile(file);
|
||||||
{
|
|
||||||
await UploadSummaryFile(file);
|
|
||||||
}
|
|
||||||
else if (String.Equals(file.Type, CoreAttachmentType.ResultsLog, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
if (file.RecordId != _jobTimelineRecordId)
|
|
||||||
{
|
|
||||||
Trace.Info($"Got a step log file to send to results service.");
|
|
||||||
await UploadResultsStepLogFile(file);
|
|
||||||
}
|
|
||||||
else if (file.RecordId == _jobTimelineRecordId)
|
|
||||||
{
|
|
||||||
Trace.Info($"Got a job log file to send to results service.");
|
|
||||||
await UploadResultsJobLogFile(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
var issue = new Issue() { Type = IssueType.Warning, Message = $"Caught exception during file upload to results. {ex.Message}" };
|
Trace.Info("Catch exception during summary file upload to results, keep going since the process is best effort.");
|
||||||
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.ResultsUploadFailure;
|
|
||||||
|
|
||||||
var telemetryRecord = new TimelineRecord()
|
|
||||||
{
|
|
||||||
Id = Constants.Runner.TelemetryRecordId,
|
|
||||||
};
|
|
||||||
telemetryRecord.Issues.Add(issue);
|
|
||||||
QueueTimelineRecordUpdate(_jobTimelineId, telemetryRecord);
|
|
||||||
|
|
||||||
Trace.Info("Catch exception during file upload to results, keep going since the process is best effort.");
|
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
errorCount++;
|
errorCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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} summary files to results, success rate: {1}/{0}.", filesToUpload.Count, filesToUpload.Count - errorCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (runOnce)
|
if (runOnce)
|
||||||
@@ -537,7 +491,7 @@ namespace GitHub.Runner.Common
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await Task.Delay(_delayForResultsUploadDequeue);
|
await Task.Delay(_delayForSummaryUploadDequeue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -814,45 +768,16 @@ namespace GitHub.Runner.Common
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UploadSummaryFile(ResultsUploadFileInfo file)
|
private async Task UploadSummaryFile(SummaryUploadFileInfo file)
|
||||||
{
|
|
||||||
Trace.Info($"Starting to upload summary file to results service {file.Name}, {file.Path}");
|
|
||||||
ResultsFileUploadHandler summaryHandler = async (file) =>
|
|
||||||
{
|
|
||||||
await _jobServer.CreateStepSummaryAsync(file.PlanId, file.JobId, file.RecordId, file.Path, CancellationToken.None);
|
|
||||||
};
|
|
||||||
|
|
||||||
await UploadResultsFile(file, summaryHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UploadResultsStepLogFile(ResultsUploadFileInfo file)
|
|
||||||
{
|
|
||||||
Trace.Info($"Starting upload of step log file to results service {file.Name}, {file.Path}");
|
|
||||||
ResultsFileUploadHandler stepLogHandler = async (file) =>
|
|
||||||
{
|
|
||||||
await _jobServer.CreateResultsStepLogAsync(file.PlanId, file.JobId, file.RecordId, file.Path, file.Finalize, file.FirstBlock, file.TotalLines, CancellationToken.None);
|
|
||||||
};
|
|
||||||
|
|
||||||
await UploadResultsFile(file, stepLogHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UploadResultsJobLogFile(ResultsUploadFileInfo file)
|
|
||||||
{
|
|
||||||
Trace.Info($"Starting upload of job log file to results service {file.Name}, {file.Path}");
|
|
||||||
ResultsFileUploadHandler jobLogHandler = async (file) =>
|
|
||||||
{
|
|
||||||
await _jobServer.CreateResultsJobLogAsync(file.PlanId, file.JobId, file.Path, file.Finalize, file.FirstBlock, file.TotalLines, CancellationToken.None);
|
|
||||||
};
|
|
||||||
|
|
||||||
await UploadResultsFile(file, jobLogHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UploadResultsFile(ResultsUploadFileInfo file, ResultsFileUploadHandler uploadHandler)
|
|
||||||
{
|
{
|
||||||
bool uploadSucceed = false;
|
bool uploadSucceed = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await uploadHandler(file);
|
// Upload the step summary
|
||||||
|
Trace.Info($"Starting to upload summary file to results service {file.Name}, {file.Path}");
|
||||||
|
var cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
await _jobServer.CreateStepSymmaryAsync(file.PlanId, file.JobId, file.StepId, file.Path, cancellationTokenSource.Token);
|
||||||
|
|
||||||
uploadSucceed = true;
|
uploadSucceed = true;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -865,7 +790,7 @@ namespace GitHub.Runner.Common
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Trace.Info("Exception encountered during deletion of a temporary file that was already successfully uploaded to results.");
|
Trace.Info("Catch exception during delete success results uploaded summary file.");
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -889,18 +814,16 @@ namespace GitHub.Runner.Common
|
|||||||
public bool DeleteSource { get; set; }
|
public bool DeleteSource { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class ResultsUploadFileInfo
|
internal class SummaryUploadFileInfo
|
||||||
{
|
{
|
||||||
|
public Guid TimelineId { get; set; }
|
||||||
|
public Guid TimelineRecordId { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Type { get; set; }
|
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
public string PlanId { get; set; }
|
public string PlanId { get; set; }
|
||||||
public string JobId { get; set; }
|
public string JobId { get; set; }
|
||||||
public Guid RecordId { get; set; }
|
public string StepId { get; set; }
|
||||||
public bool DeleteSource { get; set; }
|
public bool DeleteSource { get; set; }
|
||||||
public bool Finalize { get; set; }
|
|
||||||
public bool FirstBlock { get; set; }
|
|
||||||
public long TotalLines { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,12 +21,6 @@ namespace GitHub.Runner.Common
|
|||||||
// 8 MB
|
// 8 MB
|
||||||
public const int PageSize = 8 * 1024 * 1024;
|
public const int PageSize = 8 * 1024 * 1024;
|
||||||
|
|
||||||
// For Results
|
|
||||||
public static string BlocksFolder = "blocks";
|
|
||||||
|
|
||||||
// 2 MB
|
|
||||||
public const int BlockSize = 2 * 1024 * 1024;
|
|
||||||
|
|
||||||
private Guid _timelineId;
|
private Guid _timelineId;
|
||||||
private Guid _timelineRecordId;
|
private Guid _timelineRecordId;
|
||||||
private FileStream _pageData;
|
private FileStream _pageData;
|
||||||
@@ -38,13 +32,6 @@ namespace GitHub.Runner.Common
|
|||||||
private string _pagesFolder;
|
private string _pagesFolder;
|
||||||
private IJobServerQueue _jobServerQueue;
|
private IJobServerQueue _jobServerQueue;
|
||||||
|
|
||||||
private string _resultsDataFileName;
|
|
||||||
private FileStream _resultsBlockData;
|
|
||||||
private StreamWriter _resultsBlockWriter;
|
|
||||||
private string _resultsBlockFolder;
|
|
||||||
private int _blockByteCount;
|
|
||||||
private int _blockCount;
|
|
||||||
|
|
||||||
public long TotalLines => _totalLines;
|
public long TotalLines => _totalLines;
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
public override void Initialize(IHostContext hostContext)
|
||||||
@@ -52,10 +39,8 @@ namespace GitHub.Runner.Common
|
|||||||
base.Initialize(hostContext);
|
base.Initialize(hostContext);
|
||||||
_totalLines = 0;
|
_totalLines = 0;
|
||||||
_pagesFolder = Path.Combine(hostContext.GetDirectory(WellKnownDirectory.Diag), PagingFolder);
|
_pagesFolder = Path.Combine(hostContext.GetDirectory(WellKnownDirectory.Diag), PagingFolder);
|
||||||
Directory.CreateDirectory(_pagesFolder);
|
|
||||||
_resultsBlockFolder = Path.Combine(hostContext.GetDirectory(WellKnownDirectory.Diag), BlocksFolder);
|
|
||||||
Directory.CreateDirectory(_resultsBlockFolder);
|
|
||||||
_jobServerQueue = HostContext.GetService<IJobServerQueue>();
|
_jobServerQueue = HostContext.GetService<IJobServerQueue>();
|
||||||
|
Directory.CreateDirectory(_pagesFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Setup(Guid timelineId, Guid timelineRecordId)
|
public void Setup(Guid timelineId, Guid timelineRecordId)
|
||||||
@@ -75,17 +60,11 @@ namespace GitHub.Runner.Common
|
|||||||
// lazy creation on write
|
// lazy creation on write
|
||||||
if (_pageWriter == null)
|
if (_pageWriter == null)
|
||||||
{
|
{
|
||||||
NewPage();
|
Create();
|
||||||
}
|
|
||||||
|
|
||||||
if (_resultsBlockWriter == null)
|
|
||||||
{
|
|
||||||
NewBlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string line = $"{DateTime.UtcNow.ToString("O")} {message}";
|
string line = $"{DateTime.UtcNow.ToString("O")} {message}";
|
||||||
_pageWriter.WriteLine(line);
|
_pageWriter.WriteLine(line);
|
||||||
_resultsBlockWriter.WriteLine(line);
|
|
||||||
|
|
||||||
_totalLines++;
|
_totalLines++;
|
||||||
if (line.IndexOf('\n') != -1)
|
if (line.IndexOf('\n') != -1)
|
||||||
@@ -99,25 +78,21 @@ namespace GitHub.Runner.Common
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var bytes = System.Text.Encoding.UTF8.GetByteCount(line);
|
_byteCount += System.Text.Encoding.UTF8.GetByteCount(line);
|
||||||
_byteCount += bytes;
|
|
||||||
_blockByteCount += bytes;
|
|
||||||
if (_byteCount >= PageSize)
|
if (_byteCount >= PageSize)
|
||||||
{
|
{
|
||||||
NewPage();
|
NewPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_blockByteCount >= BlockSize)
|
|
||||||
{
|
|
||||||
NewBlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void End()
|
public void End()
|
||||||
{
|
{
|
||||||
EndPage();
|
EndPage();
|
||||||
EndBlock(true);
|
}
|
||||||
|
|
||||||
|
private void Create()
|
||||||
|
{
|
||||||
|
NewPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NewPage()
|
private void NewPage()
|
||||||
@@ -142,27 +117,5 @@ namespace GitHub.Runner.Common
|
|||||||
_jobServerQueue.QueueFileUpload(_timelineId, _timelineRecordId, "DistributedTask.Core.Log", "CustomToolLog", _dataFileName, true);
|
_jobServerQueue.QueueFileUpload(_timelineId, _timelineRecordId, "DistributedTask.Core.Log", "CustomToolLog", _dataFileName, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NewBlock()
|
|
||||||
{
|
|
||||||
EndBlock(false);
|
|
||||||
_blockByteCount = 0;
|
|
||||||
_resultsDataFileName = Path.Combine(_resultsBlockFolder, $"{_timelineId}_{_timelineRecordId}.{++_blockCount}");
|
|
||||||
_resultsBlockData = new FileStream(_resultsDataFileName, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.ReadWrite);
|
|
||||||
_resultsBlockWriter = new StreamWriter(_resultsBlockData, System.Text.Encoding.UTF8);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EndBlock(bool finalize)
|
|
||||||
{
|
|
||||||
if (_resultsBlockWriter != null)
|
|
||||||
{
|
|
||||||
_resultsBlockWriter.Flush();
|
|
||||||
_resultsBlockData.Flush();
|
|
||||||
_resultsBlockWriter.Dispose();
|
|
||||||
_resultsBlockWriter = null;
|
|
||||||
_resultsBlockData = null;
|
|
||||||
_jobServerQueue.QueueResultsUpload(_timelineRecordId, "ResultsLog", _resultsDataFileName, "Results.Core.Log", deleteSource: true, finalize, firstBlock: _resultsDataFileName.EndsWith(".1"), totalLines: _totalLines);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.Actions.RunService.WebApi;
|
|
||||||
using GitHub.DistributedTask.Pipelines;
|
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 GitHub.Services.WebApi;
|
||||||
using Sdk.WebApi.WebApi.RawClient;
|
using Sdk.WebApi.WebApi.RawClient;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common
|
namespace GitHub.Runner.Common
|
||||||
@@ -18,10 +16,6 @@ namespace GitHub.Runner.Common
|
|||||||
Task ConnectAsync(Uri serverUrl, VssCredentials credentials);
|
Task ConnectAsync(Uri serverUrl, VssCredentials credentials);
|
||||||
|
|
||||||
Task<AgentJobRequestMessage> GetJobMessageAsync(string id, CancellationToken token);
|
Task<AgentJobRequestMessage> GetJobMessageAsync(string id, CancellationToken token);
|
||||||
|
|
||||||
Task CompleteJobAsync(Guid planId, Guid jobId, TaskResult result, Dictionary<String, VariableValue> outputs, IList<StepResult> stepResults, CancellationToken token);
|
|
||||||
|
|
||||||
Task<RenewJobResponse> RenewJobAsync(Guid planId, Guid jobId, CancellationToken token);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class RunServer : RunnerService, IRunServer
|
public sealed class RunServer : RunnerService, IRunServer
|
||||||
@@ -35,7 +29,7 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
requestUri = serverUri;
|
requestUri = serverUri;
|
||||||
|
|
||||||
_connection = VssUtil.CreateRawConnection(serverUri, credentials);
|
_connection = VssUtil.CreateRawConnection(new Uri(serverUri.Authority), credentials);
|
||||||
_runServiceHttpClient = await _connection.GetClientAsync<RunServiceHttpClient>();
|
_runServiceHttpClient = await _connection.GetClientAsync<RunServiceHttpClient>();
|
||||||
_hasConnection = true;
|
_hasConnection = true;
|
||||||
}
|
}
|
||||||
@@ -61,24 +55,5 @@ namespace GitHub.Runner.Common
|
|||||||
return jobMessage;
|
return jobMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task CompleteJobAsync(Guid planId, Guid jobId, TaskResult result, Dictionary<String, VariableValue> outputs, IList<StepResult> stepResults, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
CheckConnection();
|
|
||||||
return RetryRequest(
|
|
||||||
async () => await _runServiceHttpClient.CompleteJobAsync(requestUri, planId, jobId, result, outputs, stepResults, cancellationToken), cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<RenewJobResponse> RenewJobAsync(Guid planId, Guid jobId, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
CheckConnection();
|
|
||||||
var renewJobResponse = RetryRequest<RenewJobResponse>(
|
|
||||||
async () => await _runServiceHttpClient.RenewJobAsync(requestUri, planId, jobId, cancellationToken), cancellationToken);
|
|
||||||
if (renewJobResponse == null)
|
|
||||||
{
|
|
||||||
throw new TaskOrchestrationJobNotFoundException(jobId.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
return renewJobResponse;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,237 +0,0 @@
|
|||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using GitHub.Services.WebApi;
|
|
||||||
using GitHub.Services.Common;
|
|
||||||
using GitHub.Runner.Sdk;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Common
|
|
||||||
{
|
|
||||||
[ServiceLocator(Default = typeof(RunnerDotcomServer))]
|
|
||||||
public interface IRunnerDotcomServer : IRunnerService
|
|
||||||
{
|
|
||||||
Task<List<TaskAgent>> GetRunnersAsync(int runnerGroupId, string githubUrl, string githubToken, string agentName);
|
|
||||||
|
|
||||||
Task<DistributedTask.WebApi.Runner> AddRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey);
|
|
||||||
Task<List<TaskAgentPool>> GetRunnerGroupsAsync(string githubUrl, string githubToken);
|
|
||||||
|
|
||||||
string GetGitHubRequestId(HttpResponseHeaders headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum RequestType
|
|
||||||
{
|
|
||||||
Get,
|
|
||||||
Post,
|
|
||||||
Patch,
|
|
||||||
Delete
|
|
||||||
}
|
|
||||||
|
|
||||||
public class RunnerDotcomServer : RunnerService, IRunnerDotcomServer
|
|
||||||
{
|
|
||||||
private ITerminal _term;
|
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
|
||||||
{
|
|
||||||
base.Initialize(hostContext);
|
|
||||||
_term = hostContext.GetService<ITerminal>();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async Task<List<TaskAgent>> GetRunnersAsync(int runnerGroupId, string githubUrl, string githubToken, string agentName = null)
|
|
||||||
{
|
|
||||||
var githubApiUrl = "";
|
|
||||||
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
|
||||||
var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
if (path.Length == 1)
|
|
||||||
{
|
|
||||||
// org runner
|
|
||||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/orgs/{path[0]}/actions/runner-groups/{runnerGroupId}/runners";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/orgs/{path[0]}/actions/runner-groups/{runnerGroupId}/runners";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (path.Length == 2)
|
|
||||||
{
|
|
||||||
// repo or enterprise runner.
|
|
||||||
if (!string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/{path[0]}/{path[1]}/actions/runner-groups/{runnerGroupId}/runners";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/{path[0]}/{path[1]}/actions/runner-groups/{runnerGroupId}/runners";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new ArgumentException($"'{githubUrl}' should point to an org or enterprise.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var runnersList = await RetryRequest<ListRunnersResponse>(githubApiUrl, githubToken, RequestType.Get, 3, "Failed to get agents pools");
|
|
||||||
var agents = runnersList.ToTaskAgents();
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(agentName))
|
|
||||||
{
|
|
||||||
return agents;
|
|
||||||
}
|
|
||||||
|
|
||||||
return agents.Where(x => string.Equals(x.Name, agentName, StringComparison.OrdinalIgnoreCase)).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<TaskAgentPool>> GetRunnerGroupsAsync(string githubUrl, string githubToken)
|
|
||||||
{
|
|
||||||
var githubApiUrl = "";
|
|
||||||
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
|
||||||
var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
if (path.Length == 1)
|
|
||||||
{
|
|
||||||
// org runner
|
|
||||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/orgs/{path[0]}/actions/runner-groups";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/orgs/{path[0]}/actions/runner-groups";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (path.Length == 2)
|
|
||||||
{
|
|
||||||
// repo or enterprise runner.
|
|
||||||
if (!string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/{path[0]}/{path[1]}/actions/runner-groups";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/{path[0]}/{path[1]}/actions/runner-groups";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new ArgumentException($"'{githubUrl}' should point to an org or enterprise.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var agentPools = await RetryRequest<RunnerGroupList>(githubApiUrl, githubToken, RequestType.Get, 3, "Failed to get agents pools");
|
|
||||||
|
|
||||||
return agentPools?.ToAgentPoolList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<DistributedTask.WebApi.Runner> AddRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey)
|
|
||||||
{
|
|
||||||
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
|
||||||
var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
string githubApiUrl;
|
|
||||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/actions/runners/register";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/actions/runners/register";
|
|
||||||
}
|
|
||||||
|
|
||||||
var bodyObject = new Dictionary<string, Object>()
|
|
||||||
{
|
|
||||||
{"url", githubUrl},
|
|
||||||
{"group_id", runnerGroupId},
|
|
||||||
{"name", agent.Name},
|
|
||||||
{"version", agent.Version},
|
|
||||||
{"updates_disabled", agent.DisableUpdate},
|
|
||||||
{"ephemeral", agent.Ephemeral},
|
|
||||||
{"labels", agent.Labels},
|
|
||||||
{"public_key", publicKey}
|
|
||||||
};
|
|
||||||
|
|
||||||
var body = new StringContent(StringUtil.ConvertToJson(bodyObject), null, "application/json");
|
|
||||||
|
|
||||||
return await RetryRequest<DistributedTask.WebApi.Runner>(githubApiUrl, githubToken, RequestType.Post, 3, "Failed to add agent", body);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<T> RetryRequest<T>(string githubApiUrl, string githubToken, RequestType requestType, int maxRetryAttemptsCount = 5, string errorMessage = null, StringContent body = null)
|
|
||||||
{
|
|
||||||
int retry = 0;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
retry++;
|
|
||||||
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
|
||||||
using (var httpClient = new HttpClient(httpClientHandler))
|
|
||||||
{
|
|
||||||
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("RemoteAuth", githubToken);
|
|
||||||
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
|
||||||
|
|
||||||
var responseStatus = System.Net.HttpStatusCode.OK;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
HttpResponseMessage response = null;
|
|
||||||
if (requestType == RequestType.Get)
|
|
||||||
{
|
|
||||||
response = await httpClient.GetAsync(githubApiUrl);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
response = await httpClient.PostAsync(githubApiUrl, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response != null)
|
|
||||||
{
|
|
||||||
responseStatus = response.StatusCode;
|
|
||||||
var githubRequestId = GetGitHubRequestId(response.Headers);
|
|
||||||
|
|
||||||
if (response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
Trace.Info($"Http response code: {response.StatusCode} from '{requestType.ToString()} {githubApiUrl}' ({githubRequestId})");
|
|
||||||
var jsonResponse = await response.Content.ReadAsStringAsync();
|
|
||||||
return StringUtil.ConvertFromJson<T>(jsonResponse);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_term.WriteError($"Http response code: {response.StatusCode} from '{requestType.ToString()} {githubApiUrl}' (Request Id: {githubRequestId})");
|
|
||||||
var errorResponse = await response.Content.ReadAsStringAsync();
|
|
||||||
_term.WriteError(errorResponse);
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (Exception ex) when (retry < maxRetryAttemptsCount && responseStatus != System.Net.HttpStatusCode.NotFound)
|
|
||||||
{
|
|
||||||
Trace.Error($"{errorMessage} -- Atempt: {retry}");
|
|
||||||
Trace.Error(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5));
|
|
||||||
Trace.Info($"Retrying in {backOff.Seconds} seconds");
|
|
||||||
await Task.Delay(backOff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetGitHubRequestId(HttpResponseHeaders headers)
|
|
||||||
{
|
|
||||||
if (headers.TryGetValues("x-github-request-id", out var headerValues))
|
|
||||||
{
|
|
||||||
return headerValues.FirstOrDefault();
|
|
||||||
}
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -68,19 +68,6 @@ namespace GitHub.Runner.Common
|
|||||||
throw new InvalidOperationException(nameof(EstablishVssConnection));
|
throw new InvalidOperationException(nameof(EstablishVssConnection));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async Task RetryRequest(Func<Task> func,
|
|
||||||
CancellationToken cancellationToken,
|
|
||||||
int maxRetryAttemptsCount = 5
|
|
||||||
)
|
|
||||||
{
|
|
||||||
async Task<Unit> wrappedFunc()
|
|
||||||
{
|
|
||||||
await func();
|
|
||||||
return Unit.Value;
|
|
||||||
}
|
|
||||||
await RetryRequest<Unit>(wrappedFunc, cancellationToken, maxRetryAttemptsCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task<T> RetryRequest<T>(Func<Task<T>> func,
|
protected async Task<T> RetryRequest<T>(Func<Task<T>> func,
|
||||||
CancellationToken cancellationToken,
|
CancellationToken cancellationToken,
|
||||||
int maxRetryAttemptsCount = 5
|
int maxRetryAttemptsCount = 5
|
||||||
@@ -98,7 +85,7 @@ namespace GitHub.Runner.Common
|
|||||||
// TODO: Add handling of non-retriable exceptions: https://github.com/github/actions-broker/issues/122
|
// TODO: Add handling of non-retriable exceptions: https://github.com/github/actions-broker/issues/122
|
||||||
catch (Exception ex) when (retryCount < maxRetryAttemptsCount)
|
catch (Exception ex) when (retryCount < maxRetryAttemptsCount)
|
||||||
{
|
{
|
||||||
Trace.Error("Catch exception during request");
|
Trace.Error("Catch exception during get full job message");
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15));
|
var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15));
|
||||||
Trace.Warning($"Back off {backOff.TotalSeconds} seconds before next retry. {maxRetryAttemptsCount - retryCount} attempt left.");
|
Trace.Warning($"Back off {backOff.TotalSeconds} seconds before next retry. {maxRetryAttemptsCount - retryCount} attempt left.");
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
using GitHub.Runner.Sdk;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using GitHub.Runner.Sdk;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Common
|
namespace GitHub.Runner.Common
|
||||||
{
|
{
|
||||||
@@ -24,16 +24,9 @@ namespace GitHub.Runner.Common
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(message))
|
WriteHeader(source, eventType, id);
|
||||||
{
|
WriteLine(message);
|
||||||
var messageLines = message.Split(Environment.NewLine);
|
WriteFooter(eventCache);
|
||||||
foreach (var messageLine in messageLines)
|
|
||||||
{
|
|
||||||
WriteHeader(source, eventType, id);
|
|
||||||
WriteLine(messageLine);
|
|
||||||
WriteFooter(eventCache);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool IsEnabled(TraceOptions opts)
|
internal bool IsEnabled(TraceOptions opts)
|
||||||
@@ -93,4 +86,5 @@ namespace GitHub.Runner.Common
|
|||||||
IndentLevel--;
|
IndentLevel--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
// Represents absence of value.
|
|
||||||
namespace GitHub.Runner.Common
|
|
||||||
{
|
|
||||||
public readonly struct Unit
|
|
||||||
{
|
|
||||||
public static readonly Unit Value = default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
namespace GitHub.Runner.Common.Util
|
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
|
|
||||||
public static class MessageUtil
|
|
||||||
{
|
|
||||||
public static bool IsRunServiceJob(string messageType)
|
|
||||||
{
|
|
||||||
return string.Equals(messageType, JobRequestMessageTypes.RunnerJobRequest, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
using GitHub.Runner.Common;
|
|
||||||
using GitHub.Runner.Listener.Configuration;
|
|
||||||
using GitHub.Runner.Sdk;
|
|
||||||
using GitHub.Services.Common;
|
|
||||||
using GitHub.Runner.Common.Util;
|
|
||||||
using GitHub.Services.OAuth;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Listener
|
|
||||||
{
|
|
||||||
public sealed class BrokerMessageListener : RunnerService, IMessageListener
|
|
||||||
{
|
|
||||||
private RunnerSettings _settings;
|
|
||||||
private ITerminal _term;
|
|
||||||
private TimeSpan _getNextMessageRetryInterval;
|
|
||||||
private TaskAgentStatus runnerStatus = TaskAgentStatus.Online;
|
|
||||||
private CancellationTokenSource _getMessagesTokenSource;
|
|
||||||
private IBrokerServer _brokerServer;
|
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
|
||||||
{
|
|
||||||
base.Initialize(hostContext);
|
|
||||||
|
|
||||||
_term = HostContext.GetService<ITerminal>();
|
|
||||||
_brokerServer = HostContext.GetService<IBrokerServer>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Boolean> CreateSessionAsync(CancellationToken token)
|
|
||||||
{
|
|
||||||
await RefreshBrokerConnection();
|
|
||||||
return await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task DeleteSessionAsync()
|
|
||||||
{
|
|
||||||
await Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnJobStatus(object sender, JobStatusEventArgs e)
|
|
||||||
{
|
|
||||||
Trace.Info("Received job status event. JobState: {0}", e.Status);
|
|
||||||
runnerStatus = e.Status;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_getMessagesTokenSource?.Cancel();
|
|
||||||
}
|
|
||||||
catch (ObjectDisposedException)
|
|
||||||
{
|
|
||||||
Trace.Info("_getMessagesTokenSource is already disposed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<TaskAgentMessage> GetNextMessageAsync(CancellationToken token)
|
|
||||||
{
|
|
||||||
bool encounteringError = false;
|
|
||||||
int continuousError = 0;
|
|
||||||
Stopwatch heartbeat = new();
|
|
||||||
heartbeat.Restart();
|
|
||||||
var maxRetryCount = 10;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
TaskAgentMessage message = null;
|
|
||||||
_getMessagesTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
message = await _brokerServer.GetRunnerMessageAsync(_getMessagesTokenSource.Token, runnerStatus, BuildConstants.RunnerPackage.Version);
|
|
||||||
|
|
||||||
if (message == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException) when (_getMessagesTokenSource.Token.IsCancellationRequested && !token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
Trace.Info("Get messages has been cancelled using local token source. Continue to get messages with new status.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
Trace.Info("Get next message has been cancelled.");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (TaskAgentAccessTokenExpiredException)
|
|
||||||
{
|
|
||||||
Trace.Info("Runner OAuth token has been revoked. Unable to pull message.");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (AccessDeniedException e) when (e.InnerException is InvalidTaskAgentVersionException)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Trace.Error("Catch exception during get next message.");
|
|
||||||
Trace.Error(ex);
|
|
||||||
|
|
||||||
if (!IsGetNextMessageExceptionRetriable(ex))
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
continuousError++;
|
|
||||||
//retry after a random backoff to avoid service throttling
|
|
||||||
//in case of there is a service error happened and all agents get kicked off of the long poll and all agent try to reconnect back at the same time.
|
|
||||||
if (continuousError <= 5)
|
|
||||||
{
|
|
||||||
// random backoff [15, 30]
|
|
||||||
_getNextMessageRetryInterval = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(30), _getNextMessageRetryInterval);
|
|
||||||
}
|
|
||||||
else if (continuousError >= maxRetryCount)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// more aggressive backoff [30, 60]
|
|
||||||
_getNextMessageRetryInterval = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(60), _getNextMessageRetryInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!encounteringError)
|
|
||||||
{
|
|
||||||
//print error only on the first consecutive error
|
|
||||||
_term.WriteError($"{DateTime.UtcNow:u}: Runner connect error: {ex.Message}. Retrying until reconnected.");
|
|
||||||
encounteringError = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// re-create VssConnection before next retry
|
|
||||||
await RefreshBrokerConnection();
|
|
||||||
|
|
||||||
Trace.Info("Sleeping for {0} seconds before retrying.", _getNextMessageRetryInterval.TotalSeconds);
|
|
||||||
await HostContext.Delay(_getNextMessageRetryInterval, token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_getMessagesTokenSource.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message == null)
|
|
||||||
{
|
|
||||||
if (heartbeat.Elapsed > TimeSpan.FromMinutes(30))
|
|
||||||
{
|
|
||||||
Trace.Info($"No message retrieved within last 30 minutes.");
|
|
||||||
heartbeat.Restart();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Trace.Verbose($"No message retrieved.");
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Trace.Info($"Message '{message.MessageId}' received.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task DeleteMessageAsync(TaskAgentMessage message)
|
|
||||||
{
|
|
||||||
await Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsGetNextMessageExceptionRetriable(Exception ex)
|
|
||||||
{
|
|
||||||
if (ex is TaskAgentNotFoundException ||
|
|
||||||
ex is TaskAgentPoolNotFoundException ||
|
|
||||||
ex is TaskAgentSessionExpiredException ||
|
|
||||||
ex is AccessDeniedException ||
|
|
||||||
ex is VssUnauthorizedException)
|
|
||||||
{
|
|
||||||
Trace.Info($"Non-retriable exception: {ex.Message}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Trace.Info($"Retriable exception: {ex.Message}");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RefreshBrokerConnection()
|
|
||||||
{
|
|
||||||
var configManager = HostContext.GetService<IConfigurationManager>();
|
|
||||||
_settings = configManager.LoadSettings();
|
|
||||||
|
|
||||||
if (_settings.ServerUrlV2 == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("ServerUrlV2 is not set");
|
|
||||||
}
|
|
||||||
|
|
||||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
|
||||||
VssCredentials creds = credMgr.LoadCredentials();
|
|
||||||
await _brokerServer.ConnectAsync(new Uri(_settings.ServerUrlV2), creds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -56,8 +56,7 @@ namespace GitHub.Runner.Listener
|
|||||||
new string[]
|
new string[]
|
||||||
{
|
{
|
||||||
Constants.Runner.CommandLine.Args.Token,
|
Constants.Runner.CommandLine.Args.Token,
|
||||||
Constants.Runner.CommandLine.Args.PAT,
|
Constants.Runner.CommandLine.Args.PAT
|
||||||
Constants.Runner.CommandLine.Flags.Local
|
|
||||||
},
|
},
|
||||||
// Valid run flags and args
|
// Valid run flags and args
|
||||||
[Constants.Runner.CommandLine.Commands.Run] =
|
[Constants.Runner.CommandLine.Commands.Run] =
|
||||||
@@ -87,7 +86,6 @@ namespace GitHub.Runner.Listener
|
|||||||
public bool Help => TestFlag(Constants.Runner.CommandLine.Flags.Help);
|
public bool Help => TestFlag(Constants.Runner.CommandLine.Flags.Help);
|
||||||
public bool Unattended => TestFlag(Constants.Runner.CommandLine.Flags.Unattended);
|
public bool Unattended => TestFlag(Constants.Runner.CommandLine.Flags.Unattended);
|
||||||
public bool Version => TestFlag(Constants.Runner.CommandLine.Flags.Version);
|
public bool Version => TestFlag(Constants.Runner.CommandLine.Flags.Version);
|
||||||
public bool RemoveLocalConfig => TestFlag(Constants.Runner.CommandLine.Flags.Local);
|
|
||||||
|
|
||||||
// Keep this around since customers still relies on it
|
// Keep this around since customers still relies on it
|
||||||
public bool RunOnce => TestFlag(Constants.Runner.CommandLine.Flags.Once);
|
public bool RunOnce => TestFlag(Constants.Runner.CommandLine.Flags.Once);
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Common.Util;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
using GitHub.Services.Common;
|
||||||
|
using GitHub.Services.Common.Internal;
|
||||||
|
using GitHub.Services.OAuth;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -7,14 +14,6 @@ using System.Runtime.InteropServices;
|
|||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
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.Common.Internal;
|
|
||||||
using GitHub.Services.OAuth;
|
|
||||||
using GitHub.Services.WebApi.Jwt;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Listener.Configuration
|
namespace GitHub.Runner.Listener.Configuration
|
||||||
{
|
{
|
||||||
@@ -32,14 +31,12 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
{
|
{
|
||||||
private IConfigurationStore _store;
|
private IConfigurationStore _store;
|
||||||
private IRunnerServer _runnerServer;
|
private IRunnerServer _runnerServer;
|
||||||
private IRunnerDotcomServer _dotcomServer;
|
|
||||||
private ITerminal _term;
|
private ITerminal _term;
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
public override void Initialize(IHostContext hostContext)
|
||||||
{
|
{
|
||||||
base.Initialize(hostContext);
|
base.Initialize(hostContext);
|
||||||
_runnerServer = HostContext.GetService<IRunnerServer>();
|
_runnerServer = HostContext.GetService<IRunnerServer>();
|
||||||
_dotcomServer = HostContext.GetService<IRunnerDotcomServer>();
|
|
||||||
Trace.Verbose("Creating _store");
|
Trace.Verbose("Creating _store");
|
||||||
_store = hostContext.GetService<IConfigurationStore>();
|
_store = hostContext.GetService<IConfigurationStore>();
|
||||||
Trace.Verbose("store created");
|
Trace.Verbose("store created");
|
||||||
@@ -116,7 +113,6 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
ICredentialProvider credProvider = null;
|
ICredentialProvider credProvider = null;
|
||||||
VssCredentials creds = null;
|
VssCredentials creds = null;
|
||||||
_term.WriteSection("Authentication");
|
_term.WriteSection("Authentication");
|
||||||
string registerToken = string.Empty;
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
// When testing against a dev deployment of Actions Service, set this environment variable
|
// When testing against a dev deployment of Actions Service, set this environment variable
|
||||||
@@ -134,11 +130,9 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
runnerSettings.GitHubUrl = inputUrl;
|
runnerSettings.GitHubUrl = inputUrl;
|
||||||
registerToken = await GetRunnerTokenAsync(command, inputUrl, "registration");
|
var registerToken = await GetRunnerTokenAsync(command, inputUrl, "registration");
|
||||||
GitHubAuthResult authResult = await GetTenantCredential(inputUrl, registerToken, Constants.RunnerEvent.Register);
|
GitHubAuthResult authResult = await GetTenantCredential(inputUrl, registerToken, Constants.RunnerEvent.Register);
|
||||||
runnerSettings.ServerUrl = authResult.TenantUrl;
|
runnerSettings.ServerUrl = authResult.TenantUrl;
|
||||||
runnerSettings.UseV2Flow = authResult.UseV2Flow;
|
|
||||||
_term.WriteLine($"Using V2 flow: {runnerSettings.UseV2Flow}");
|
|
||||||
creds = authResult.ToVssCredentials();
|
creds = authResult.ToVssCredentials();
|
||||||
Trace.Info("cred retrieved via GitHub auth");
|
Trace.Info("cred retrieved via GitHub auth");
|
||||||
}
|
}
|
||||||
@@ -182,11 +176,9 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
// We want to use the native CSP of the platform for storage, so we use the RSACSP directly
|
// We want to use the native CSP of the platform for storage, so we use the RSACSP directly
|
||||||
RSAParameters publicKey;
|
RSAParameters publicKey;
|
||||||
var keyManager = HostContext.GetService<IRSAKeyManager>();
|
var keyManager = HostContext.GetService<IRSAKeyManager>();
|
||||||
string publicKeyXML;
|
|
||||||
using (var rsa = keyManager.CreateKey())
|
using (var rsa = keyManager.CreateKey())
|
||||||
{
|
{
|
||||||
publicKey = rsa.ExportParameters(false);
|
publicKey = rsa.ExportParameters(false);
|
||||||
publicKeyXML = rsa.ToXmlString(includePrivateParameters: false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_term.WriteSection("Runner Registration");
|
_term.WriteSection("Runner Registration");
|
||||||
@@ -194,17 +186,9 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
// If we have more than one runner group available, allow the user to specify which one to be added into
|
// If we have more than one runner group available, allow the user to specify which one to be added into
|
||||||
string poolName = null;
|
string poolName = null;
|
||||||
TaskAgentPool agentPool = null;
|
TaskAgentPool agentPool = null;
|
||||||
List<TaskAgentPool> agentPools;
|
List<TaskAgentPool> agentPools = await _runnerServer.GetAgentPoolsAsync();
|
||||||
if (runnerSettings.UseV2Flow)
|
|
||||||
{
|
|
||||||
agentPools = await _dotcomServer.GetRunnerGroupsAsync(runnerSettings.GitHubUrl, registerToken);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
agentPools = await _runnerServer.GetAgentPoolsAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskAgentPool defaultPool = agentPools?.Where(x => x.IsInternal).FirstOrDefault();
|
TaskAgentPool defaultPool = agentPools?.Where(x => x.IsInternal).FirstOrDefault();
|
||||||
|
|
||||||
if (agentPools?.Where(x => !x.IsHosted).Count() > 0)
|
if (agentPools?.Where(x => !x.IsHosted).Count() > 0)
|
||||||
{
|
{
|
||||||
poolName = command.GetRunnerGroupName(defaultPool?.Name);
|
poolName = command.GetRunnerGroupName(defaultPool?.Name);
|
||||||
@@ -242,16 +226,8 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
|
|
||||||
var userLabels = command.GetLabels();
|
var userLabels = command.GetLabels();
|
||||||
_term.WriteLine();
|
_term.WriteLine();
|
||||||
List<TaskAgent> agents;
|
|
||||||
if (runnerSettings.UseV2Flow)
|
|
||||||
{
|
|
||||||
agents = await _dotcomServer.GetRunnersAsync(runnerSettings.PoolId, runnerSettings.GitHubUrl, registerToken, runnerSettings.AgentName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
agents = await _runnerServer.GetAgentsAsync(runnerSettings.PoolId, runnerSettings.AgentName);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
var agents = await _runnerServer.GetAgentsAsync(runnerSettings.PoolId, runnerSettings.AgentName);
|
||||||
Trace.Verbose("Returns {0} agents", agents.Count);
|
Trace.Verbose("Returns {0} agents", agents.Count);
|
||||||
agent = agents.FirstOrDefault();
|
agent = agents.FirstOrDefault();
|
||||||
if (agent != null)
|
if (agent != null)
|
||||||
@@ -298,17 +274,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (runnerSettings.UseV2Flow)
|
agent = await _runnerServer.AddAgentAsync(runnerSettings.PoolId, agent);
|
||||||
{
|
|
||||||
var runner = await _dotcomServer.AddRunnerAsync(runnerSettings.PoolId, agent, runnerSettings.GitHubUrl, registerToken, publicKeyXML);
|
|
||||||
runner.ApplyToTaskAgent(agent);
|
|
||||||
runnerSettings.ServerUrlV2 = runner.RunnerAuthorization.ServerUrl;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
agent = await _runnerServer.AddAgentAsync(runnerSettings.PoolId, agent);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.DisableUpdate &&
|
if (command.DisableUpdate &&
|
||||||
command.DisableUpdate != agent.DisableUpdate)
|
command.DisableUpdate != agent.DisableUpdate)
|
||||||
{
|
{
|
||||||
@@ -359,28 +325,24 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Testing agent connection, detect any potential connection issue, like local clock skew that cause OAuth token expired.
|
// Testing agent connection, detect any potential connection issue, like local clock skew that cause OAuth token expired.
|
||||||
|
var credMgr = HostContext.GetService<ICredentialManager>();
|
||||||
if (!runnerSettings.UseV2Flow)
|
VssCredentials credential = credMgr.LoadCredentials();
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), credential);
|
||||||
VssCredentials credential = credMgr.LoadCredentials();
|
// ConnectAsync() hits _apis/connectionData which is an anonymous endpoint
|
||||||
try
|
// Need to hit an authenticate endpoint to trigger OAuth token exchange.
|
||||||
{
|
await _runnerServer.GetAgentPoolsAsync();
|
||||||
await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), credential);
|
_term.WriteSuccessMessage("Runner connection is good");
|
||||||
// ConnectAsync() hits _apis/connectionData which is an anonymous endpoint
|
}
|
||||||
// Need to hit an authenticate endpoint to trigger OAuth token exchange.
|
catch (VssOAuthTokenRequestException ex) when (ex.Message.Contains("Current server time is"))
|
||||||
await _runnerServer.GetAgentPoolsAsync();
|
{
|
||||||
_term.WriteSuccessMessage("Runner connection is good");
|
// there are two exception messages server send that indicate clock skew.
|
||||||
}
|
// 1. The bearer token expired on {jwt.ValidTo}. Current server time is {DateTime.UtcNow}.
|
||||||
catch (VssOAuthTokenRequestException ex) when (ex.Message.Contains("Current server time is"))
|
// 2. The bearer token is not valid until {jwt.ValidFrom}. Current server time is {DateTime.UtcNow}.
|
||||||
{
|
Trace.Error("Catch exception during test agent connection.");
|
||||||
// there are two exception messages server send that indicate clock skew.
|
Trace.Error(ex);
|
||||||
// 1. The bearer token expired on {jwt.ValidTo}. Current server time is {DateTime.UtcNow}.
|
throw new Exception("The local machine's clock may be out of sync with the server time by more than five minutes. Please sync your clock with your domain or internet time and try again.");
|
||||||
// 2. The bearer token is not valid until {jwt.ValidFrom}. Current server time is {DateTime.UtcNow}.
|
|
||||||
Trace.Error("Catch exception during test agent connection.");
|
|
||||||
Trace.Error(ex);
|
|
||||||
throw new Exception("The local machine's clock may be out of sync with the server time by more than five minutes. Please sync your clock with your domain or internet time and try again.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_term.WriteSection("Runner settings");
|
_term.WriteSection("Runner settings");
|
||||||
@@ -674,7 +636,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
}
|
}
|
||||||
|
|
||||||
int retryCount = 0;
|
int retryCount = 0;
|
||||||
while (retryCount < 3)
|
while(retryCount < 3)
|
||||||
{
|
{
|
||||||
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
||||||
using (var httpClient = new HttpClient(httpClientHandler))
|
using (var httpClient = new HttpClient(httpClientHandler))
|
||||||
@@ -684,29 +646,28 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("basic", base64EncodingToken);
|
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("basic", base64EncodingToken);
|
||||||
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
||||||
httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/vnd.github.v3+json");
|
httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/vnd.github.v3+json");
|
||||||
|
|
||||||
var responseStatus = System.Net.HttpStatusCode.OK;
|
var responseStatus = System.Net.HttpStatusCode.OK;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await httpClient.PostAsync(githubApiUrl, new StringContent(string.Empty));
|
var response = await httpClient.PostAsync(githubApiUrl, new StringContent(string.Empty));
|
||||||
responseStatus = response.StatusCode;
|
responseStatus = response.StatusCode;
|
||||||
var githubRequestId = _dotcomServer.GetGitHubRequestId(response.Headers);
|
|
||||||
|
|
||||||
if (response.IsSuccessStatusCode)
|
if (response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
Trace.Info($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}' ({githubRequestId})");
|
Trace.Info($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'");
|
||||||
var jsonResponse = await response.Content.ReadAsStringAsync();
|
var jsonResponse = await response.Content.ReadAsStringAsync();
|
||||||
return StringUtil.ConvertFromJson<GitHubRunnerRegisterToken>(jsonResponse);
|
return StringUtil.ConvertFromJson<GitHubRunnerRegisterToken>(jsonResponse);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}' (Request Id: {githubRequestId})");
|
_term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'");
|
||||||
var errorResponse = await response.Content.ReadAsStringAsync();
|
var errorResponse = await response.Content.ReadAsStringAsync();
|
||||||
_term.WriteError(errorResponse);
|
_term.WriteError(errorResponse);
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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 -- Atempt: {retryCount}");
|
Trace.Error($"Failed to get JIT runner token -- Atempt: {retryCount}");
|
||||||
@@ -753,23 +714,22 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
{
|
{
|
||||||
var response = await httpClient.PostAsync(githubApiUrl, new StringContent(StringUtil.ConvertToJson(bodyObject), null, "application/json"));
|
var response = await httpClient.PostAsync(githubApiUrl, new StringContent(StringUtil.ConvertToJson(bodyObject), null, "application/json"));
|
||||||
responseStatus = response.StatusCode;
|
responseStatus = response.StatusCode;
|
||||||
var githubRequestId = _dotcomServer.GetGitHubRequestId(response.Headers);
|
|
||||||
|
|
||||||
if (response.IsSuccessStatusCode)
|
if(response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
Trace.Info($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}' ({githubRequestId})");
|
Trace.Info($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'");
|
||||||
var jsonResponse = await response.Content.ReadAsStringAsync();
|
var jsonResponse = await response.Content.ReadAsStringAsync();
|
||||||
return StringUtil.ConvertFromJson<GitHubAuthResult>(jsonResponse);
|
return StringUtil.ConvertFromJson<GitHubAuthResult>(jsonResponse);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}' (Request Id: {githubRequestId})");
|
_term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'");
|
||||||
var errorResponse = await response.Content.ReadAsStringAsync();
|
var errorResponse = await response.Content.ReadAsStringAsync();
|
||||||
_term.WriteError(errorResponse);
|
_term.WriteError(errorResponse);
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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 -- Atempt: {retryCount}");
|
Trace.Error($"Failed to get tenant credentials -- Atempt: {retryCount}");
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
@@ -20,8 +20,8 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
{
|
{
|
||||||
public static readonly Dictionary<string, Type> CredentialTypes = new(StringComparer.OrdinalIgnoreCase)
|
public static readonly Dictionary<string, Type> CredentialTypes = new(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
{ Constants.Configuration.OAuth, typeof(OAuthCredential) },
|
{ Constants.Configuration.OAuth, typeof(OAuthCredential)},
|
||||||
{ Constants.Configuration.OAuthAccessToken, typeof(OAuthAccessTokenCredential) },
|
{ Constants.Configuration.OAuthAccessToken, typeof(OAuthAccessTokenCredential)},
|
||||||
};
|
};
|
||||||
|
|
||||||
public ICredentialProvider GetCredentialProvider(string credType)
|
public ICredentialProvider GetCredentialProvider(string credType)
|
||||||
@@ -93,9 +93,6 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
[DataMember(Name = "token")]
|
[DataMember(Name = "token")]
|
||||||
public string Token { get; set; }
|
public string Token { get; set; }
|
||||||
|
|
||||||
[DataMember(Name = "use_v2_flow")]
|
|
||||||
public bool UseV2Flow { get; set; }
|
|
||||||
|
|
||||||
public VssCredentials ToVssCredentials()
|
public VssCredentials ToVssCredentials()
|
||||||
{
|
{
|
||||||
ArgUtil.NotNullOrEmpty(TokenSchema, nameof(TokenSchema));
|
ArgUtil.NotNullOrEmpty(TokenSchema, nameof(TokenSchema));
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
[assembly: InternalsVisibleTo("Test")]
|
|
||||||
@@ -7,7 +7,6 @@ using System.Text;
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.Pipelines;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
using GitHub.Runner.Common.Util;
|
using GitHub.Runner.Common.Util;
|
||||||
@@ -59,8 +58,6 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
public event EventHandler<JobStatusEventArgs> JobStatus;
|
public event EventHandler<JobStatusEventArgs> JobStatus;
|
||||||
|
|
||||||
private bool _isRunServiceJob;
|
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
public override void Initialize(IHostContext hostContext)
|
||||||
{
|
{
|
||||||
base.Initialize(hostContext);
|
base.Initialize(hostContext);
|
||||||
@@ -89,8 +86,6 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
Trace.Info($"Job request {jobRequestMessage.RequestId} for plan {jobRequestMessage.Plan.PlanId} job {jobRequestMessage.JobId} received.");
|
Trace.Info($"Job request {jobRequestMessage.RequestId} for plan {jobRequestMessage.Plan.PlanId} job {jobRequestMessage.JobId} received.");
|
||||||
|
|
||||||
_isRunServiceJob = MessageUtil.IsRunServiceJob(jobRequestMessage.MessageType);
|
|
||||||
|
|
||||||
WorkerDispatcher currentDispatch = null;
|
WorkerDispatcher currentDispatch = null;
|
||||||
if (_jobDispatchedQueue.Count > 0)
|
if (_jobDispatchedQueue.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -244,13 +239,6 @@ namespace GitHub.Runner.Listener
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._isRunServiceJob)
|
|
||||||
{
|
|
||||||
Trace.Error($"We are not yet checking the state of jobrequest {jobDispatch.JobId} status. Cancel running worker right away.");
|
|
||||||
jobDispatch.WorkerCancellationTokenSource.Cancel();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// based on the current design, server will only send one job for a given runner at a time.
|
// based on the current design, server will only send one job for a given runner at a time.
|
||||||
// if the runner received a new job request while a previous job request is still running, this typically indicates two situations
|
// if the runner received a new job request while a previous job request is still running, this typically indicates two situations
|
||||||
// 1. a runner bug caused a server and runner mismatch on the state of the job request, e.g. the runner didn't renew the jobrequest
|
// 1. a runner bug caused a server and runner mismatch on the state of the job request, e.g. the runner didn't renew the jobrequest
|
||||||
@@ -379,11 +367,9 @@ namespace GitHub.Runner.Listener
|
|||||||
long requestId = message.RequestId;
|
long requestId = message.RequestId;
|
||||||
Guid lockToken = Guid.Empty; // lockToken has never been used, keep this here of compat
|
Guid lockToken = Guid.Empty; // lockToken has never been used, keep this here of compat
|
||||||
|
|
||||||
var systemConnection = message.Resources.Endpoints.SingleOrDefault(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
// start renew job request
|
// start renew job request
|
||||||
Trace.Info($"Start renew job request {requestId} for job {message.JobId}.");
|
Trace.Info($"Start renew job request {requestId} for job {message.JobId}.");
|
||||||
Task renewJobRequest = RenewJobRequestAsync(message, systemConnection, _poolId, requestId, lockToken, orchestrationId, firstJobRequestRenewed, lockRenewalTokenSource.Token);
|
Task renewJobRequest = RenewJobRequestAsync(_poolId, requestId, lockToken, orchestrationId, firstJobRequestRenewed, lockRenewalTokenSource.Token);
|
||||||
|
|
||||||
// wait till first renew succeed or job request is cancelled
|
// wait till first renew succeed or job request is cancelled
|
||||||
// not even start worker if the first renew fail
|
// not even start worker if the first renew fail
|
||||||
@@ -440,7 +426,7 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
workerOutput.Add(stdout.Data);
|
workerOutput.Add(stdout.Data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (printToStdout)
|
if (printToStdout)
|
||||||
{
|
{
|
||||||
term.WriteLine(stdout.Data, skipTracing: true);
|
term.WriteLine(stdout.Data, skipTracing: true);
|
||||||
@@ -522,6 +508,7 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
// we get first jobrequest renew succeed and start the worker process with the job message.
|
// we get first jobrequest renew succeed and start the worker process with the job message.
|
||||||
// send notification to machine provisioner.
|
// send notification to machine provisioner.
|
||||||
|
var systemConnection = message.Resources.Endpoints.SingleOrDefault(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
||||||
var accessToken = systemConnection?.Authorization?.Parameters["AccessToken"];
|
var accessToken = systemConnection?.Authorization?.Parameters["AccessToken"];
|
||||||
notification.JobStarted(message.JobId, accessToken, systemConnection.Url);
|
notification.JobStarted(message.JobId, accessToken, systemConnection.Url);
|
||||||
|
|
||||||
@@ -544,8 +531,11 @@ namespace GitHub.Runner.Listener
|
|||||||
detailInfo = string.Join(Environment.NewLine, workerOutput);
|
detailInfo = string.Join(Environment.NewLine, workerOutput);
|
||||||
Trace.Info($"Return code {returnCode} indicate worker encounter an unhandled exception or app crash, attach worker stdout/stderr to JobRequest result.");
|
Trace.Info($"Return code {returnCode} indicate worker encounter an unhandled exception or app crash, attach worker stdout/stderr to JobRequest result.");
|
||||||
|
|
||||||
|
var jobServer = HostContext.GetService<IJobServer>();
|
||||||
|
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
|
||||||
|
VssConnection jobConnection = VssUtil.CreateConnection(systemConnection.Url, jobServerCredential);
|
||||||
|
await jobServer.ConnectAsync(jobConnection);
|
||||||
|
|
||||||
var jobServer = await InitializeJobServerAsync(systemConnection);
|
|
||||||
await LogWorkerProcessUnhandledException(jobServer, message, detailInfo);
|
await LogWorkerProcessUnhandledException(jobServer, message, detailInfo);
|
||||||
|
|
||||||
// Go ahead to finish the job with result 'Failed' if the STDERR from worker is System.IO.IOException, since it typically means we are running out of disk space.
|
// Go ahead to finish the job with result 'Failed' if the STDERR from worker is System.IO.IOException, since it typically means we are running out of disk space.
|
||||||
@@ -685,128 +675,9 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task RenewJobRequestAsync(Pipelines.AgentJobRequestMessage message, ServiceEndpoint systemConnection, int poolId, long requestId, Guid lockToken, string orchestrationId, TaskCompletionSource<int> firstJobRequestRenewed, CancellationToken token)
|
public async Task RenewJobRequestAsync(int poolId, long requestId, Guid lockToken, string orchestrationId, TaskCompletionSource<int> firstJobRequestRenewed, CancellationToken token)
|
||||||
{
|
|
||||||
if (this._isRunServiceJob)
|
|
||||||
{
|
|
||||||
var runServer = await GetRunServerAsync(systemConnection);
|
|
||||||
await RenewJobRequestAsync(runServer, message.Plan.PlanId, message.JobId, firstJobRequestRenewed, token);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var runnerServer = HostContext.GetService<IRunnerServer>();
|
|
||||||
await RenewJobRequestAsync(runnerServer, poolId, requestId, lockToken, orchestrationId, firstJobRequestRenewed, token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RenewJobRequestAsync(IRunServer runServer, Guid planId, Guid jobId, TaskCompletionSource<int> firstJobRequestRenewed, CancellationToken token)
|
|
||||||
{
|
|
||||||
TaskAgentJobRequest request = null;
|
|
||||||
int firstRenewRetryLimit = 5;
|
|
||||||
int encounteringError = 0;
|
|
||||||
|
|
||||||
// renew lock during job running.
|
|
||||||
// stop renew only if cancellation token for lock renew task been signal or exception still happen after retry.
|
|
||||||
while (!token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var renewResponse = await runServer.RenewJobAsync(planId, jobId, token);
|
|
||||||
Trace.Info($"Successfully renew job {jobId}, job is valid till {renewResponse.LockedUntil}");
|
|
||||||
|
|
||||||
if (!firstJobRequestRenewed.Task.IsCompleted)
|
|
||||||
{
|
|
||||||
// fire first renew succeed event.
|
|
||||||
firstJobRequestRenewed.TrySetResult(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (encounteringError > 0)
|
|
||||||
{
|
|
||||||
encounteringError = 0;
|
|
||||||
HostContext.WritePerfCounter("JobRenewRecovered");
|
|
||||||
}
|
|
||||||
|
|
||||||
// renew again after 60 sec delay
|
|
||||||
await HostContext.Delay(TimeSpan.FromSeconds(60), token);
|
|
||||||
}
|
|
||||||
catch (TaskOrchestrationJobNotFoundException)
|
|
||||||
{
|
|
||||||
// no need for retry. the job is not valid anymore.
|
|
||||||
Trace.Info($"TaskAgentJobNotFoundException received when renew job {jobId}, job is no longer valid, stop renew job request.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
// OperationCanceledException may caused by http timeout or _lockRenewalTokenSource.Cance();
|
|
||||||
// Stop renew only on cancellation token fired.
|
|
||||||
Trace.Info($"job renew has been cancelled, stop renew job {jobId}.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Trace.Error($"Catch exception during renew runner job {jobId}.");
|
|
||||||
Trace.Error(ex);
|
|
||||||
encounteringError++;
|
|
||||||
|
|
||||||
// retry
|
|
||||||
TimeSpan remainingTime = TimeSpan.Zero;
|
|
||||||
if (!firstJobRequestRenewed.Task.IsCompleted)
|
|
||||||
{
|
|
||||||
// retry 5 times every 10 sec for the first renew
|
|
||||||
if (firstRenewRetryLimit-- > 0)
|
|
||||||
{
|
|
||||||
remainingTime = TimeSpan.FromSeconds(10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// retry till reach lockeduntil + 5 mins extra buffer.
|
|
||||||
remainingTime = request.LockedUntil.Value + TimeSpan.FromMinutes(5) - DateTime.UtcNow;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remainingTime > TimeSpan.Zero)
|
|
||||||
{
|
|
||||||
TimeSpan delayTime;
|
|
||||||
if (!firstJobRequestRenewed.Task.IsCompleted)
|
|
||||||
{
|
|
||||||
Trace.Info($"Retrying lock renewal for job {jobId}. The first job renew request has failed.");
|
|
||||||
delayTime = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(10));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Trace.Info($"Retrying lock renewal for job {jobId}. Job is valid until {request.LockedUntil.Value}.");
|
|
||||||
if (encounteringError > 5)
|
|
||||||
{
|
|
||||||
delayTime = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(30));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
delayTime = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// back-off before next retry.
|
|
||||||
await HostContext.Delay(delayTime, token);
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
Trace.Info($"job renew has been cancelled, stop renew job {jobId}.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Trace.Info($"Lock renewal has run out of retry, stop renew lock for job {jobId}.");
|
|
||||||
HostContext.WritePerfCounter("JobRenewReachLimit");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RenewJobRequestAsync(IRunnerServer runnerServer, int poolId, long requestId, Guid lockToken, string orchestrationId, TaskCompletionSource<int> firstJobRequestRenewed, CancellationToken token)
|
|
||||||
{
|
{
|
||||||
|
var runnerServer = HostContext.GetService<IRunnerServer>();
|
||||||
TaskAgentJobRequest request = null;
|
TaskAgentJobRequest request = null;
|
||||||
int firstRenewRetryLimit = 5;
|
int firstRenewRetryLimit = 5;
|
||||||
int encounteringError = 0;
|
int encounteringError = 0;
|
||||||
@@ -969,93 +840,90 @@ namespace GitHub.Runner.Listener
|
|||||||
var systemConnection = message.Resources.Endpoints.SingleOrDefault(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection));
|
var systemConnection = message.Resources.Endpoints.SingleOrDefault(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection));
|
||||||
ArgUtil.NotNull(systemConnection, nameof(systemConnection));
|
ArgUtil.NotNull(systemConnection, nameof(systemConnection));
|
||||||
|
|
||||||
var server = await InitializeJobServerAsync(systemConnection);
|
var jobServer = HostContext.GetService<IJobServer>();
|
||||||
|
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
|
||||||
|
VssConnection jobConnection = VssUtil.CreateConnection(systemConnection.Url, jobServerCredential);
|
||||||
|
|
||||||
if (server is IJobServer jobServer)
|
await jobServer.ConnectAsync(jobConnection);
|
||||||
|
|
||||||
|
var timeline = await jobServer.GetTimelineAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, CancellationToken.None);
|
||||||
|
|
||||||
|
var updatedRecords = new List<TimelineRecord>();
|
||||||
|
var logPages = new Dictionary<Guid, Dictionary<int, string>>();
|
||||||
|
var logRecords = new Dictionary<Guid, TimelineRecord>();
|
||||||
|
foreach (var log in logs)
|
||||||
{
|
{
|
||||||
var timeline = await jobServer.GetTimelineAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, CancellationToken.None);
|
var logName = Path.GetFileNameWithoutExtension(log);
|
||||||
|
var logNameParts = logName.Split('_', StringSplitOptions.RemoveEmptyEntries);
|
||||||
var updatedRecords = new List<TimelineRecord>();
|
if (logNameParts.Length != 3)
|
||||||
var logPages = new Dictionary<Guid, Dictionary<int, string>>();
|
|
||||||
var logRecords = new Dictionary<Guid, TimelineRecord>();
|
|
||||||
foreach (var log in logs)
|
|
||||||
{
|
{
|
||||||
var logName = Path.GetFileNameWithoutExtension(log);
|
Trace.Warning($"log file '{log}' doesn't follow naming convension 'GUID_GUID_INT'.");
|
||||||
var logNameParts = logName.Split('_', StringSplitOptions.RemoveEmptyEntries);
|
continue;
|
||||||
if (logNameParts.Length != 3)
|
}
|
||||||
{
|
var logPageSeperator = logName.IndexOf('_');
|
||||||
Trace.Warning($"log file '{log}' doesn't follow naming convension 'GUID_GUID_INT'.");
|
var logRecordId = Guid.Empty;
|
||||||
continue;
|
var pageNumber = 0;
|
||||||
}
|
|
||||||
var logPageSeperator = logName.IndexOf('_');
|
|
||||||
var logRecordId = Guid.Empty;
|
|
||||||
var pageNumber = 0;
|
|
||||||
|
|
||||||
if (!Guid.TryParse(logNameParts[0], out Guid timelineId) || timelineId != timeline.Id)
|
if (!Guid.TryParse(logNameParts[0], out Guid timelineId) || timelineId != timeline.Id)
|
||||||
{
|
{
|
||||||
Trace.Warning($"log file '{log}' is not belongs to current job");
|
Trace.Warning($"log file '{log}' is not belongs to current job");
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
if (!Guid.TryParse(logNameParts[1], out logRecordId))
|
|
||||||
{
|
|
||||||
Trace.Warning($"log file '{log}' doesn't follow naming convension 'GUID_GUID_INT'.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!int.TryParse(logNameParts[2], out pageNumber))
|
|
||||||
{
|
|
||||||
Trace.Warning($"log file '{log}' doesn't follow naming convension 'GUID_GUID_INT'.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var record = timeline.Records.FirstOrDefault(x => x.Id == logRecordId);
|
|
||||||
if (record != null)
|
|
||||||
{
|
|
||||||
if (!logPages.ContainsKey(record.Id))
|
|
||||||
{
|
|
||||||
logPages[record.Id] = new Dictionary<int, string>();
|
|
||||||
logRecords[record.Id] = record;
|
|
||||||
}
|
|
||||||
|
|
||||||
logPages[record.Id][pageNumber] = log;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var pages in logPages)
|
if (!Guid.TryParse(logNameParts[1], out logRecordId))
|
||||||
{
|
{
|
||||||
var record = logRecords[pages.Key];
|
Trace.Warning($"log file '{log}' doesn't follow naming convension 'GUID_GUID_INT'.");
|
||||||
if (record.Log == null)
|
continue;
|
||||||
{
|
|
||||||
// Create the log
|
|
||||||
record.Log = await jobServer.CreateLogAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, new TaskLog(String.Format(@"logs\{0:D}", record.Id)), default(CancellationToken));
|
|
||||||
|
|
||||||
// Need to post timeline record updates to reflect the log creation
|
|
||||||
updatedRecords.Add(record.Clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 1; i <= pages.Value.Count; i++)
|
|
||||||
{
|
|
||||||
var logFile = pages.Value[i];
|
|
||||||
// Upload the contents
|
|
||||||
using (FileStream fs = File.Open(logFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
|
||||||
{
|
|
||||||
var logUploaded = await jobServer.AppendLogContentAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, record.Log.Id, fs, default(CancellationToken));
|
|
||||||
}
|
|
||||||
|
|
||||||
Trace.Info($"Uploaded unfinished log '{logFile}' for current job.");
|
|
||||||
IOUtil.DeleteFile(logFile);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updatedRecords.Count > 0)
|
if (!int.TryParse(logNameParts[2], out pageNumber))
|
||||||
{
|
{
|
||||||
await jobServer.UpdateTimelineRecordsAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, updatedRecords, CancellationToken.None);
|
Trace.Warning($"log file '{log}' doesn't follow naming convension 'GUID_GUID_INT'.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var record = timeline.Records.FirstOrDefault(x => x.Id == logRecordId);
|
||||||
|
if (record != null)
|
||||||
|
{
|
||||||
|
if (!logPages.ContainsKey(record.Id))
|
||||||
|
{
|
||||||
|
logPages[record.Id] = new Dictionary<int, string>();
|
||||||
|
logRecords[record.Id] = record;
|
||||||
|
}
|
||||||
|
|
||||||
|
logPages[record.Id][pageNumber] = log;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
foreach (var pages in logPages)
|
||||||
{
|
{
|
||||||
Trace.Info("Job server does not support log upload yet.");
|
var record = logRecords[pages.Key];
|
||||||
|
if (record.Log == null)
|
||||||
|
{
|
||||||
|
// Create the log
|
||||||
|
record.Log = await jobServer.CreateLogAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, new TaskLog(String.Format(@"logs\{0:D}", record.Id)), default(CancellationToken));
|
||||||
|
|
||||||
|
// Need to post timeline record updates to reflect the log creation
|
||||||
|
updatedRecords.Add(record.Clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 1; i <= pages.Value.Count; i++)
|
||||||
|
{
|
||||||
|
var logFile = pages.Value[i];
|
||||||
|
// Upload the contents
|
||||||
|
using (FileStream fs = File.Open(logFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||||
|
{
|
||||||
|
var logUploaded = await jobServer.AppendLogContentAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, record.Log.Id, fs, default(CancellationToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
Trace.Info($"Uploaded unfinished log '{logFile}' for current job.");
|
||||||
|
IOUtil.DeleteFile(logFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updatedRecords.Count > 0)
|
||||||
|
{
|
||||||
|
await jobServer.UpdateTimelineRecordsAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, updatedRecords, CancellationToken.None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -1075,12 +943,6 @@ namespace GitHub.Runner.Listener
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._isRunServiceJob)
|
|
||||||
{
|
|
||||||
Trace.Verbose($"Skip FinishAgentRequest call from Listener because MessageType is {message.MessageType}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var runnerServer = HostContext.GetService<IRunnerServer>();
|
var runnerServer = HostContext.GetService<IRunnerServer>();
|
||||||
int completeJobRequestRetryLimit = 5;
|
int completeJobRequestRetryLimit = 5;
|
||||||
List<Exception> exceptions = new();
|
List<Exception> exceptions = new();
|
||||||
@@ -1117,117 +979,66 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
|
|
||||||
// log an error issue to job level timeline record
|
// log an error issue to job level timeline record
|
||||||
private async Task LogWorkerProcessUnhandledException(IRunnerService server, Pipelines.AgentJobRequestMessage message, string errorMessage)
|
private async Task LogWorkerProcessUnhandledException(IJobServer jobServer, Pipelines.AgentJobRequestMessage message, string errorMessage)
|
||||||
{
|
{
|
||||||
if (server is IJobServer jobServer)
|
try
|
||||||
{
|
{
|
||||||
|
var timeline = await jobServer.GetTimelineAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, CancellationToken.None);
|
||||||
|
ArgUtil.NotNull(timeline, nameof(timeline));
|
||||||
|
|
||||||
|
TimelineRecord jobRecord = timeline.Records.FirstOrDefault(x => x.Id == message.JobId && x.RecordType == "Job");
|
||||||
|
ArgUtil.NotNull(jobRecord, nameof(jobRecord));
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var timeline = await jobServer.GetTimelineAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, CancellationToken.None);
|
if (!string.IsNullOrEmpty(errorMessage) &&
|
||||||
ArgUtil.NotNull(timeline, nameof(timeline));
|
message.Variables.TryGetValue("DistributedTask.EnableRunnerIPCDebug", out var enableRunnerIPCDebug) &&
|
||||||
|
StringUtil.ConvertToBoolean(enableRunnerIPCDebug.Value))
|
||||||
TimelineRecord jobRecord = timeline.Records.FirstOrDefault(x => x.Id == message.JobId && x.RecordType == "Job");
|
|
||||||
ArgUtil.NotNull(jobRecord, nameof(jobRecord));
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(errorMessage) &&
|
// the trace should be best effort and not affect any job result
|
||||||
message.Variables.TryGetValue("DistributedTask.EnableRunnerIPCDebug", out var enableRunnerIPCDebug) &&
|
var match = _invalidJsonRegex.Match(errorMessage);
|
||||||
StringUtil.ConvertToBoolean(enableRunnerIPCDebug.Value))
|
if (match.Success &&
|
||||||
|
match.Groups.Count == 2)
|
||||||
{
|
{
|
||||||
// the trace should be best effort and not affect any job result
|
var jsonPosition = int.Parse(match.Groups[1].Value);
|
||||||
var match = _invalidJsonRegex.Match(errorMessage);
|
var serializedJobMessage = JsonUtility.ToString(message);
|
||||||
if (match.Success &&
|
var originalJson = serializedJobMessage.Substring(jsonPosition - 10, 20);
|
||||||
match.Groups.Count == 2)
|
errorMessage = $"Runner sent Json at position '{jsonPosition}': {originalJson} ({Convert.ToBase64String(Encoding.UTF8.GetBytes(originalJson))})\n{errorMessage}";
|
||||||
{
|
|
||||||
var jsonPosition = int.Parse(match.Groups[1].Value);
|
|
||||||
var serializedJobMessage = JsonUtility.ToString(message);
|
|
||||||
var originalJson = serializedJobMessage.Substring(jsonPosition - 10, 20);
|
|
||||||
errorMessage = $"Runner sent Json at position '{jsonPosition}': {originalJson} ({Convert.ToBase64String(Encoding.UTF8.GetBytes(originalJson))})\n{errorMessage}";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Trace.Error(ex);
|
|
||||||
errorMessage = $"Fail to check json IPC error: {ex.Message}\n{errorMessage}";
|
|
||||||
}
|
|
||||||
|
|
||||||
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = errorMessage };
|
|
||||||
unhandledExceptionIssue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.WorkerCrash;
|
|
||||||
jobRecord.ErrorCount++;
|
|
||||||
jobRecord.Issues.Add(unhandledExceptionIssue);
|
|
||||||
await jobServer.UpdateTimelineRecordsAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, new TimelineRecord[] { jobRecord }, CancellationToken.None);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Trace.Error("Fail to report unhandled exception from Runner.Worker process");
|
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
|
errorMessage = $"Fail to check json IPC error: {ex.Message}\n{errorMessage}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = errorMessage };
|
||||||
|
unhandledExceptionIssue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.WorkerCrash;
|
||||||
|
jobRecord.ErrorCount++;
|
||||||
|
jobRecord.Issues.Add(unhandledExceptionIssue);
|
||||||
|
await jobServer.UpdateTimelineRecordsAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, new TimelineRecord[] { jobRecord }, CancellationToken.None);
|
||||||
}
|
}
|
||||||
else
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Trace.Info("Job server does not support handling unhandled exception yet, error message: {0}", errorMessage);
|
Trace.Error("Fail to report unhandled exception from Runner.Worker process");
|
||||||
return;
|
Trace.Error(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// raise job completed event to fail the job.
|
// raise job completed event to fail the job.
|
||||||
private async Task ForceFailJob(IRunnerService server, Pipelines.AgentJobRequestMessage message)
|
private async Task ForceFailJob(IJobServer jobServer, Pipelines.AgentJobRequestMessage message)
|
||||||
{
|
{
|
||||||
if (server is IJobServer jobServer)
|
try
|
||||||
{
|
{
|
||||||
try
|
var jobCompletedEvent = new JobCompletedEvent(message.RequestId, message.JobId, TaskResult.Failed);
|
||||||
{
|
await jobServer.RaisePlanEventAsync<JobCompletedEvent>(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, jobCompletedEvent, CancellationToken.None);
|
||||||
var jobCompletedEvent = new JobCompletedEvent(message.RequestId, message.JobId, TaskResult.Failed);
|
|
||||||
await jobServer.RaisePlanEventAsync<JobCompletedEvent>(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, jobCompletedEvent, CancellationToken.None);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Trace.Error("Fail to raise JobCompletedEvent back to service.");
|
|
||||||
Trace.Error(ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (server is IRunServer runServer)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
try
|
Trace.Error("Fail to raise JobCompletedEvent back to service.");
|
||||||
{
|
Trace.Error(ex);
|
||||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, TaskResult.Failed, outputs: null, stepResults: null, CancellationToken.None);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Trace.Error("Fail to raise job completion back to service.");
|
|
||||||
Trace.Error(ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new NotSupportedException($"Server type {server.GetType().FullName} is not supported.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<IRunnerService> InitializeJobServerAsync(ServiceEndpoint systemConnection)
|
|
||||||
{
|
|
||||||
if (this._isRunServiceJob)
|
|
||||||
{
|
|
||||||
return await GetRunServerAsync(systemConnection);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var jobServer = HostContext.GetService<IJobServer>();
|
|
||||||
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
|
|
||||||
VssConnection jobConnection = VssUtil.CreateConnection(systemConnection.Url, jobServerCredential);
|
|
||||||
await jobServer.ConnectAsync(jobConnection);
|
|
||||||
return jobServer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<IRunServer> GetRunServerAsync(ServiceEndpoint systemConnection)
|
|
||||||
{
|
|
||||||
var runServer = HostContext.GetService<IRunServer>();
|
|
||||||
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
|
|
||||||
await runServer.ConnectAsync(systemConnection.Url, jobServerCredential);
|
|
||||||
return runServer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class WorkerDispatcher : IDisposable
|
private class WorkerDispatcher : IDisposable
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ namespace GitHub.Runner.Listener
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_getMessagesTokenSource?.Cancel();
|
_getMessagesTokenSource?.Cancel();
|
||||||
}
|
}
|
||||||
catch (ObjectDisposedException)
|
catch (ObjectDisposedException)
|
||||||
{
|
{
|
||||||
Trace.Info("_getMessagesTokenSource is already disposed.");
|
Trace.Info("_getMessagesTokenSource is already disposed.");
|
||||||
@@ -245,10 +245,6 @@ namespace GitHub.Runner.Listener
|
|||||||
_accessTokenRevoked = true;
|
_accessTokenRevoked = true;
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
catch (AccessDeniedException e) when (e.InnerException is InvalidTaskAgentVersionException)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Trace.Error("Catch exception during get next message.");
|
Trace.Error("Catch exception during get next message.");
|
||||||
@@ -293,7 +289,7 @@ namespace GitHub.Runner.Listener
|
|||||||
await HostContext.Delay(_getNextMessageRetryInterval, token);
|
await HostContext.Delay(_getNextMessageRetryInterval, token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_getMessagesTokenSource.Dispose();
|
_getMessagesTokenSource.Dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ using System.IO;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Listener
|
namespace GitHub.Runner.Listener
|
||||||
{
|
{
|
||||||
@@ -59,7 +58,7 @@ namespace GitHub.Runner.Listener
|
|||||||
terminal.WriteLine("This runner version is built for Windows. Please install a correct build for your OS.");
|
terminal.WriteLine("This runner version is built for Windows. Please install a correct build for your OS.");
|
||||||
return Constants.Runner.ReturnCode.TerminatedError;
|
return Constants.Runner.ReturnCode.TerminatedError;
|
||||||
}
|
}
|
||||||
#if ARM64
|
#if ARM64
|
||||||
// A little hacky, but windows gives no way to differentiate between windows 10 and 11.
|
// A little hacky, but windows gives no way to differentiate between windows 10 and 11.
|
||||||
// By default only 11 supports native x64 app emulation on arm, so we only want to support windows 11
|
// By default only 11 supports native x64 app emulation on arm, so we only want to support windows 11
|
||||||
// https://docs.microsoft.com/en-us/windows/arm/overview#build-windows-apps-that-run-on-arm
|
// https://docs.microsoft.com/en-us/windows/arm/overview#build-windows-apps-that-run-on-arm
|
||||||
@@ -70,7 +69,7 @@ namespace GitHub.Runner.Listener
|
|||||||
terminal.WriteLine("Win-arm64 runners require windows 11 or later. Please upgrade your operating system.");
|
terminal.WriteLine("Win-arm64 runners require windows 11 or later. Please upgrade your operating system.");
|
||||||
return Constants.Runner.ReturnCode.TerminatedError;
|
return Constants.Runner.ReturnCode.TerminatedError;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
terminal.WriteLine($"Running the runner on this platform is not supported. The current platform is {RuntimeInformation.OSDescription} and it was built for {Constants.Runner.Platform.ToString()}.");
|
terminal.WriteLine($"Running the runner on this platform is not supported. The current platform is {RuntimeInformation.OSDescription} and it was built for {Constants.Runner.Platform.ToString()}.");
|
||||||
@@ -138,12 +137,6 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (AccessDeniedException e) when (e.InnerException is InvalidTaskAgentVersionException)
|
|
||||||
{
|
|
||||||
terminal.WriteError($"An error occured: {e.Message}");
|
|
||||||
trace.Error(e);
|
|
||||||
return Constants.Runner.ReturnCode.TerminatedError;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
terminal.WriteError($"An error occurred: {e.Message}");
|
terminal.WriteError($"An error occurred: {e.Message}");
|
||||||
|
|||||||
@@ -4,13 +4,11 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
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;
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
using GitHub.Runner.Common.Util;
|
|
||||||
using GitHub.Runner.Listener.Check;
|
using GitHub.Runner.Listener.Check;
|
||||||
using GitHub.Runner.Listener.Configuration;
|
using GitHub.Runner.Listener.Configuration;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
@@ -137,12 +135,6 @@ namespace GitHub.Runner.Listener
|
|||||||
// remove config files, remove service, and exit
|
// remove config files, remove service, and exit
|
||||||
if (command.Remove)
|
if (command.Remove)
|
||||||
{
|
{
|
||||||
// only remove local config files and exit
|
|
||||||
if (command.RemoveLocalConfig)
|
|
||||||
{
|
|
||||||
configManager.DeleteLocalRunnerConfig();
|
|
||||||
return Constants.Runner.ReturnCode.Success;
|
|
||||||
}
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await configManager.UnconfigureAsync(command);
|
await configManager.UnconfigureAsync(command);
|
||||||
@@ -211,16 +203,10 @@ namespace GitHub.Runner.Listener
|
|||||||
foreach (var config in jitConfig)
|
foreach (var config in jitConfig)
|
||||||
{
|
{
|
||||||
var configFile = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), config.Key);
|
var configFile = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), config.Key);
|
||||||
var configContent = Convert.FromBase64String(config.Value);
|
var configContent = Encoding.UTF8.GetString(Convert.FromBase64String(config.Value));
|
||||||
#if OS_WINDOWS
|
File.WriteAllText(configFile, configContent, Encoding.UTF8);
|
||||||
if (configFile == HostContext.GetConfigFile(WellKnownConfigFile.RSACredentials))
|
|
||||||
{
|
|
||||||
configContent = ProtectedData.Protect(configContent, null, DataProtectionScope.LocalMachine);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
File.WriteAllBytes(configFile, configContent);
|
|
||||||
File.SetAttributes(configFile, File.GetAttributes(configFile) | FileAttributes.Hidden);
|
File.SetAttributes(configFile, File.GetAttributes(configFile) | FileAttributes.Hidden);
|
||||||
Trace.Info($"Saved {configContent.Length} bytes to '{configFile}'.");
|
Trace.Info($"Save {configContent.Length} chars to '{configFile}'.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -339,25 +325,13 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IMessageListener GetMesageListener(RunnerSettings settings)
|
|
||||||
{
|
|
||||||
if (settings.UseV2Flow)
|
|
||||||
{
|
|
||||||
var brokerListener = new BrokerMessageListener();
|
|
||||||
brokerListener.Initialize(HostContext);
|
|
||||||
return brokerListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
return HostContext.GetService<IMessageListener>();
|
|
||||||
}
|
|
||||||
|
|
||||||
//create worker manager, create message listener and start listening to the queue
|
//create worker manager, create message listener and start listening to the queue
|
||||||
private async Task<int> RunAsync(RunnerSettings settings, bool runOnce = false)
|
private async Task<int> RunAsync(RunnerSettings settings, bool runOnce = false)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Trace.Info(nameof(RunAsync));
|
Trace.Info(nameof(RunAsync));
|
||||||
_listener = GetMesageListener(settings);
|
_listener = HostContext.GetService<IMessageListener>();
|
||||||
if (!await _listener.CreateSessionAsync(HostContext.RunnerShutdownToken))
|
if (!await _listener.CreateSessionAsync(HostContext.RunnerShutdownToken))
|
||||||
{
|
{
|
||||||
return Constants.Runner.ReturnCode.TerminatedError;
|
return Constants.Runner.ReturnCode.TerminatedError;
|
||||||
@@ -522,7 +496,7 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Broker flow
|
// Broker flow
|
||||||
else if (MessageUtil.IsRunServiceJob(message.MessageType))
|
else if (string.Equals(message.MessageType, JobRequestMessageTypes.RunnerJobRequest, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (autoUpdateInProgress || runOnceJobReceived)
|
if (autoUpdateInProgress || runOnceJobReceived)
|
||||||
{
|
{
|
||||||
@@ -673,7 +647,6 @@ Config Options:
|
|||||||
--name string Name of the runner to configure (default {Environment.MachineName ?? "myrunner"})
|
--name string Name of the runner to configure (default {Environment.MachineName ?? "myrunner"})
|
||||||
--runnergroup string Name of the runner group to add this runner to (defaults to the default runner group)
|
--runnergroup string Name of the runner group to add this runner to (defaults to the default runner group)
|
||||||
--labels string Extra labels in addition to the default: 'self-hosted,{Constants.Runner.Platform},{Constants.Runner.PlatformArchitecture}'
|
--labels string Extra labels in addition to the default: 'self-hosted,{Constants.Runner.Platform},{Constants.Runner.PlatformArchitecture}'
|
||||||
--local Removes the runner config files from your local machine. Used as an option to the remove command
|
|
||||||
--work string Relative runner work directory (default {Constants.Path.WorkDirectory})
|
--work string Relative runner work directory (default {Constants.Path.WorkDirectory})
|
||||||
--replace Replace any existing runner with the same name (default false)
|
--replace Replace any existing runner with the same name (default false)
|
||||||
--pat GitHub personal access token with repo scope. Used for checking network connectivity when executing `.{separator}run.{ext} --check`
|
--pat GitHub personal access token with repo scope. Used for checking network connectivity when executing `.{separator}run.{ext} --check`
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ namespace GitHub.Runner.Sdk
|
|||||||
{
|
{
|
||||||
var headerValues = new List<ProductInfoHeaderValue>();
|
var headerValues = new List<ProductInfoHeaderValue>();
|
||||||
headerValues.Add(new ProductInfoHeaderValue($"GitHubActionsRunner-Plugin", BuildConstants.RunnerPackage.Version));
|
headerValues.Add(new ProductInfoHeaderValue($"GitHubActionsRunner-Plugin", BuildConstants.RunnerPackage.Version));
|
||||||
headerValues.Add(new ProductInfoHeaderValue($"({StringUtil.SanitizeUserAgentHeader(RuntimeInformation.OSDescription)})"));
|
headerValues.Add(new ProductInfoHeaderValue($"({RuntimeInformation.OSDescription.Trim()})"));
|
||||||
|
|
||||||
if (VssClientHttpRequestSettings.Default.UserAgent != null && VssClientHttpRequestSettings.Default.UserAgent.Count > 0)
|
if (VssClientHttpRequestSettings.Default.UserAgent != null && VssClientHttpRequestSettings.Default.UserAgent.Count > 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -264,17 +264,7 @@ namespace GitHub.Runner.Sdk
|
|||||||
{
|
{
|
||||||
foreach (KeyValuePair<string, string> kvp in environment)
|
foreach (KeyValuePair<string, string> kvp in environment)
|
||||||
{
|
{
|
||||||
#if OS_WINDOWS
|
|
||||||
string tempKey = String.IsNullOrWhiteSpace(kvp.Key) ? kvp.Key : kvp.Key.Split('\0')[0];
|
|
||||||
string tempValue = String.IsNullOrWhiteSpace(kvp.Value) ? kvp.Value : kvp.Value.Split('\0')[0];
|
|
||||||
if(!String.IsNullOrWhiteSpace(tempKey))
|
|
||||||
{
|
|
||||||
_proc.StartInfo.Environment[tempKey] = tempValue;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
_proc.StartInfo.Environment[kvp.Key] = kvp.Value;
|
_proc.StartInfo.Environment[kvp.Key] = kvp.Value;
|
||||||
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ namespace GitHub.Runner.Sdk
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_noProxyList.Add(noProxyInfo);
|
_noProxyList.Add(noProxyInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -206,11 +207,6 @@ namespace GitHub.Runner.Sdk
|
|||||||
{
|
{
|
||||||
foreach (var noProxy in _noProxyList)
|
foreach (var noProxy in _noProxyList)
|
||||||
{
|
{
|
||||||
// bypass on wildcard no_proxy
|
|
||||||
if (string.Equals(noProxy.Host, "*", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
var matchHost = false;
|
var matchHost = false;
|
||||||
var matchPort = false;
|
var matchPort = false;
|
||||||
|
|
||||||
|
|||||||
@@ -123,12 +123,5 @@ namespace GitHub.Runner.Sdk
|
|||||||
{
|
{
|
||||||
return value?.Substring(0, Math.Min(value.Length, count));
|
return value?.Substring(0, Math.Min(value.Length, count));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fixes format violations e.g. https://github.com/actions/runner/issues/2165
|
|
||||||
public static string SanitizeUserAgentHeader(string header)
|
|
||||||
{
|
|
||||||
return header.Replace("(", "[").Replace(")", "]").Trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,16 +6,9 @@ namespace GitHub.Runner.Sdk
|
|||||||
{
|
{
|
||||||
public static bool IsHostedServer(UriBuilder gitHubUrl)
|
public static bool IsHostedServer(UriBuilder gitHubUrl)
|
||||||
{
|
{
|
||||||
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_FORCE_GHES")))
|
return string.Equals(gitHubUrl.Host, "github.com", StringComparison.OrdinalIgnoreCase) ||
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
string.Equals(gitHubUrl.Host, "github.com", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
string.Equals(gitHubUrl.Host, "www.github.com", StringComparison.OrdinalIgnoreCase) ||
|
string.Equals(gitHubUrl.Host, "www.github.com", StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(gitHubUrl.Host, "github.localhost", StringComparison.OrdinalIgnoreCase) ||
|
string.Equals(gitHubUrl.Host, "github.localhost", StringComparison.OrdinalIgnoreCase);
|
||||||
gitHubUrl.Host.EndsWith(".ghe.com", StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Uri GetCredentialEmbeddedUrl(Uri baseUrl, string username, string password)
|
public static Uri GetCredentialEmbeddedUrl(Uri baseUrl, string username, string password)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace GitHub.Runner.Sdk
|
|||||||
{
|
{
|
||||||
var headerValues = new List<ProductInfoHeaderValue>();
|
var headerValues = new List<ProductInfoHeaderValue>();
|
||||||
headerValues.AddRange(additionalUserAgents);
|
headerValues.AddRange(additionalUserAgents);
|
||||||
headerValues.Add(new ProductInfoHeaderValue($"({StringUtil.SanitizeUserAgentHeader(RuntimeInformation.OSDescription)})"));
|
headerValues.Add(new ProductInfoHeaderValue($"({RuntimeInformation.OSDescription.Trim()})"));
|
||||||
|
|
||||||
if (VssClientHttpRequestSettings.Default.UserAgent != null && VssClientHttpRequestSettings.Default.UserAgent.Count > 0)
|
if (VssClientHttpRequestSettings.Default.UserAgent != null && VssClientHttpRequestSettings.Default.UserAgent.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -116,7 +116,7 @@ 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);
|
||||||
|
|
||||||
RawConnection connection = new(serverUri, new RawHttpMessageHandler(credentials.Federated, settings), additionalDelegatingHandler);
|
RawConnection connection = new(serverUri, new RawHttpMessageHandler(credentials.ToOAuthCredentials(), settings), additionalDelegatingHandler);
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ namespace GitHub.Runner.Worker
|
|||||||
public interface IActionRunner : IStep, IRunnerService
|
public interface IActionRunner : IStep, IRunnerService
|
||||||
{
|
{
|
||||||
ActionRunStage Stage { get; set; }
|
ActionRunStage Stage { get; set; }
|
||||||
|
bool TryEvaluateDisplayName(DictionaryContextData contextData, IExecutionContext context);
|
||||||
Pipelines.ActionStep Action { get; set; }
|
Pipelines.ActionStep Action { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,67 +285,25 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public bool TryEvaluateDisplayName(DictionaryContextData contextData, IExecutionContext context)
|
||||||
/// Attempts to update the DisplayName.
|
|
||||||
/// As the "Try..." name implies, this method should never throw an exception.
|
|
||||||
/// Returns true if the DisplayName is already present or it was successfully updated.
|
|
||||||
/// </summary>
|
|
||||||
public bool TryUpdateDisplayName(out bool updated)
|
|
||||||
{
|
|
||||||
updated = false;
|
|
||||||
|
|
||||||
// REVIEW: This try/catch can be removed if some future implementation of EvaluateDisplayName and UpdateTimelineRecordDisplayName
|
|
||||||
// can make reasonable guarantees that they won't throw an exception.
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// This attempt is only worthwhile at the "Main" stage.
|
|
||||||
// When the job starts, there's an initial attempt to evaluate the DisplayName. (see JobExtension::InitializeJob)
|
|
||||||
// During the "Pre" stage, we expect that no contexts will have changed since the initial evaluation.
|
|
||||||
// "Main" stage is handled here.
|
|
||||||
// During the "Post" stage, it no longer matters.
|
|
||||||
if (this.Stage == ActionRunStage.Main && EvaluateDisplayName(this.ExecutionContext.ExpressionValues, this.ExecutionContext, out updated))
|
|
||||||
{
|
|
||||||
if (updated)
|
|
||||||
{
|
|
||||||
this.ExecutionContext.UpdateTimelineRecordDisplayName(this.DisplayName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Trace.Warning("Caught exception while attempting to evaulate/update the step's DisplayName. Exception Details: {0}", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For consistency with other implementations of TryUpdateDisplayName we use !string.IsNullOrEmpty below,
|
|
||||||
// but note that (at the time of this writing) ActionRunner::DisplayName::get always returns a non-empty string due to its fallback logic.
|
|
||||||
// In other words, the net effect is that this particular implementation of TryUpdateDisplayName will always return true.
|
|
||||||
return !string.IsNullOrEmpty(this.DisplayName);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempts to evaluate the DisplayName of this IActionRunner.
|
|
||||||
/// Returns true if the DisplayName is already present or it was successfully evaluated.
|
|
||||||
/// </summary>
|
|
||||||
public bool EvaluateDisplayName(DictionaryContextData contextData, IExecutionContext context, out bool updated)
|
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(context, nameof(context));
|
ArgUtil.NotNull(context, nameof(context));
|
||||||
ArgUtil.NotNull(Action, nameof(Action));
|
ArgUtil.NotNull(Action, nameof(Action));
|
||||||
|
|
||||||
updated = false;
|
// If we have already expanded the display name, there is no need to expand it again
|
||||||
// If we have already expanded the display name, don't bother attempting [re-]expansion.
|
// TODO: Remove the ShouldEvaluateDisplayName check and field post m158 deploy, we should do it by default once the server is updated
|
||||||
if (_didFullyEvaluateDisplayName || !string.IsNullOrEmpty(Action.DisplayName))
|
if (_didFullyEvaluateDisplayName || !string.IsNullOrEmpty(Action.DisplayName))
|
||||||
{
|
{
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_displayName = GenerateDisplayName(Action, contextData, context, out bool didFullyEvaluate);
|
bool didFullyEvaluate;
|
||||||
|
_displayName = GenerateDisplayName(Action, contextData, context, out didFullyEvaluate);
|
||||||
|
|
||||||
// If we evaluated, fully mask any secrets
|
// If we evaluated fully mask any secrets
|
||||||
if (didFullyEvaluate)
|
if (didFullyEvaluate)
|
||||||
{
|
{
|
||||||
_displayName = HostContext.SecretMasker.MaskSecrets(_displayName);
|
_displayName = HostContext.SecretMasker.MaskSecrets(_displayName);
|
||||||
updated = true;
|
|
||||||
}
|
}
|
||||||
context.Debug($"Set step '{Action.Name}' display name to: '{_displayName}'");
|
context.Debug($"Set step '{Action.Name}' display name to: '{_displayName}'");
|
||||||
_didFullyEvaluateDisplayName = didFullyEvaluate;
|
_didFullyEvaluateDisplayName = didFullyEvaluate;
|
||||||
|
|||||||
@@ -33,14 +33,8 @@ namespace GitHub.Runner.Worker
|
|||||||
public override void Initialize(IHostContext hostContext)
|
public override void Initialize(IHostContext hostContext)
|
||||||
{
|
{
|
||||||
base.Initialize(hostContext);
|
base.Initialize(hostContext);
|
||||||
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(Constants.Hooks.ContainerHooksPath)))
|
_dockerManager = HostContext.GetService<IDockerCommandManager>();
|
||||||
{
|
_containerHookManager = HostContext.GetService<IContainerHookManager>();
|
||||||
_dockerManager = HostContext.GetService<IDockerCommandManager>();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_containerHookManager = HostContext.GetService<IContainerHookManager>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StartContainersAsync(IExecutionContext executionContext, object data)
|
public async Task StartContainersAsync(IExecutionContext executionContext, object data)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -6,7 +6,6 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.Actions.RunService.WebApi;
|
|
||||||
using GitHub.DistributedTask.Expressions2;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
@@ -81,7 +80,7 @@ namespace GitHub.Runner.Worker
|
|||||||
// logging
|
// logging
|
||||||
long Write(string tag, string message);
|
long Write(string tag, string message);
|
||||||
void QueueAttachFile(string type, string name, string filePath);
|
void QueueAttachFile(string type, string name, string filePath);
|
||||||
void QueueSummaryFile(string name, string filePath, Guid stepRecordId);
|
void QueueSummaryFile(string name, string filePath, string stepId);
|
||||||
|
|
||||||
// timeline record update methods
|
// timeline record update methods
|
||||||
void Start(string currentOperation = null);
|
void Start(string currentOperation = null);
|
||||||
@@ -438,17 +437,6 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
PublishStepTelemetry();
|
PublishStepTelemetry();
|
||||||
|
|
||||||
var stepResult = new StepResult();
|
|
||||||
stepResult.ExternalID = _record.Id;
|
|
||||||
stepResult.Conclusion = _record.Result ?? TaskResult.Succeeded;
|
|
||||||
stepResult.Status = _record.State;
|
|
||||||
stepResult.Number = _record.Order;
|
|
||||||
stepResult.Name = _record.Name;
|
|
||||||
stepResult.StartedAt = _record.StartTime;
|
|
||||||
stepResult.CompletedAt = _record.FinishTime;
|
|
||||||
|
|
||||||
Global.StepsResult.Add(stepResult);
|
|
||||||
|
|
||||||
if (Root != this)
|
if (Root != this)
|
||||||
{
|
{
|
||||||
// only dispose TokenSource for step level ExecutionContext
|
// only dispose TokenSource for step level ExecutionContext
|
||||||
@@ -722,9 +710,6 @@ namespace GitHub.Runner.Worker
|
|||||||
// ActionsStepTelemetry for entire job
|
// ActionsStepTelemetry for entire job
|
||||||
Global.StepsTelemetry = new List<ActionsStepTelemetry>();
|
Global.StepsTelemetry = new List<ActionsStepTelemetry>();
|
||||||
|
|
||||||
// Steps results for entire job
|
|
||||||
Global.StepsResult = new List<StepResult>();
|
|
||||||
|
|
||||||
// Job Outputs
|
// Job Outputs
|
||||||
JobOutputs = new Dictionary<string, VariableValue>(StringComparer.OrdinalIgnoreCase);
|
JobOutputs = new Dictionary<string, VariableValue>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
@@ -862,7 +847,7 @@ namespace GitHub.Runner.Worker
|
|||||||
_jobServerQueue.QueueFileUpload(_mainTimelineId, _record.Id, type, name, filePath, deleteSource: false);
|
_jobServerQueue.QueueFileUpload(_mainTimelineId, _record.Id, type, name, filePath, deleteSource: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void QueueSummaryFile(string name, string filePath, Guid stepRecordId)
|
public void QueueSummaryFile(string name, string filePath, string stepId)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNullOrEmpty(name, nameof(name));
|
ArgUtil.NotNullOrEmpty(name, nameof(name));
|
||||||
ArgUtil.NotNullOrEmpty(filePath, nameof(filePath));
|
ArgUtil.NotNullOrEmpty(filePath, nameof(filePath));
|
||||||
@@ -871,7 +856,8 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
throw new FileNotFoundException($"Can't upload (name:{name}) file: {filePath}. File does not exist.");
|
throw new FileNotFoundException($"Can't upload (name:{name}) file: {filePath}. File does not exist.");
|
||||||
}
|
}
|
||||||
_jobServerQueue.QueueResultsUpload(stepRecordId, name, filePath, ChecksAttachmentType.StepSummary, deleteSource: false, finalize: true, firstBlock: true, totalLines: 0);
|
|
||||||
|
_jobServerQueue.QueueSummaryUpload(_mainTimelineId, _record.Id, stepId, name, filePath, deleteSource: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add OnMatcherChanged
|
// Add OnMatcherChanged
|
||||||
|
|||||||
@@ -182,14 +182,6 @@ namespace GitHub.Runner.Worker
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileSize > AttachmentSizeLimit)
|
|
||||||
{
|
|
||||||
context.Error(String.Format(Constants.Runner.UnsupportedSummarySize, AttachmentSizeLimit / 1024, fileSize / 1024));
|
|
||||||
Trace.Info($"Step Summary file ({filePath}) is too large ({fileSize} bytes); skipping attachment upload");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Trace.Verbose($"Step Summary file exists: {filePath} and has a file size of {fileSize} bytes");
|
Trace.Verbose($"Step Summary file exists: {filePath} and has a file size of {fileSize} bytes");
|
||||||
var scrubbedFilePath = filePath + "-scrubbed";
|
var scrubbedFilePath = filePath + "-scrubbed";
|
||||||
|
|
||||||
@@ -208,19 +200,28 @@ namespace GitHub.Runner.Worker
|
|||||||
? context.Id.ToString()
|
? context.Id.ToString()
|
||||||
: context.EmbeddedId.ToString();
|
: context.EmbeddedId.ToString();
|
||||||
|
|
||||||
Trace.Info($"Queueing file ({filePath}) for attachment upload ({attachmentName})");
|
|
||||||
// Attachments must be added to the parent context (job), not the current context (step)
|
|
||||||
context.Root.QueueAttachFile(ChecksAttachmentType.StepSummary, attachmentName, scrubbedFilePath);
|
|
||||||
|
|
||||||
// Dual upload the same files to Results Service
|
|
||||||
context.Global.Variables.TryGetValue("system.github.results_endpoint", out string resultsReceiverEndpoint);
|
context.Global.Variables.TryGetValue("system.github.results_endpoint", out string resultsReceiverEndpoint);
|
||||||
if (resultsReceiverEndpoint != null)
|
if (resultsReceiverEndpoint != null)
|
||||||
{
|
{
|
||||||
Trace.Info($"Queueing results file ({filePath}) for attachment upload ({attachmentName})");
|
Trace.Info($"Queueing results file ({filePath}) for attachment upload ({attachmentName})");
|
||||||
var stepId = context.Id;
|
var stepId = context.Id.ToString();
|
||||||
// Attachments must be added to the parent context (job), not the current context (step)
|
// Attachments must be added to the parent context (job), not the current context (step)
|
||||||
context.Root.QueueSummaryFile(attachmentName, scrubbedFilePath, stepId);
|
context.Root.QueueSummaryFile(attachmentName, scrubbedFilePath, stepId);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (fileSize > AttachmentSizeLimit)
|
||||||
|
{
|
||||||
|
context.Error(String.Format(Constants.Runner.UnsupportedSummarySize, AttachmentSizeLimit / 1024, fileSize / 1024));
|
||||||
|
Trace.Info($"Step Summary file ({filePath}) is too large ({fileSize} bytes); skipping attachment upload");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Trace.Info($"Queueing file ({filePath}) for attachment upload ({attachmentName})");
|
||||||
|
// Attachments must be added to the parent context (job), not the current context (step)
|
||||||
|
context.Root.QueueAttachFile(ChecksAttachmentType.StepSummary, attachmentName, scrubbedFilePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using GitHub.Actions.RunService.WebApi;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Common.Util;
|
using GitHub.Runner.Common.Util;
|
||||||
using GitHub.Runner.Worker.Container;
|
using GitHub.Runner.Worker.Container;
|
||||||
@@ -17,7 +16,6 @@ namespace GitHub.Runner.Worker
|
|||||||
public IList<String> FileTable { get; set; }
|
public IList<String> FileTable { get; set; }
|
||||||
public IDictionary<String, IDictionary<String, String>> JobDefaults { get; set; }
|
public IDictionary<String, IDictionary<String, String>> JobDefaults { get; set; }
|
||||||
public List<ActionsStepTelemetry> StepsTelemetry { get; set; }
|
public List<ActionsStepTelemetry> StepsTelemetry { get; set; }
|
||||||
public List<StepResult> StepsResult { get; set; }
|
|
||||||
public List<JobTelemetry> JobTelemetry { get; set; }
|
public List<JobTelemetry> JobTelemetry { get; set; }
|
||||||
public TaskOrchestrationPlanReference Plan { get; set; }
|
public TaskOrchestrationPlanReference Plan { get; set; }
|
||||||
public List<string> PrependPath { get; set; }
|
public List<string> PrependPath { get; set; }
|
||||||
|
|||||||
@@ -38,17 +38,8 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
// Update the env dictionary.
|
// Update the env dictionary.
|
||||||
AddInputsToEnvironment();
|
AddInputsToEnvironment();
|
||||||
|
|
||||||
IDockerCommandManager dockerManager = null;
|
var dockerManager = HostContext.GetService<IDockerCommandManager>();
|
||||||
IContainerHookManager containerHookManager = null;
|
var containerHookManager = HostContext.GetService<IContainerHookManager>();
|
||||||
if (FeatureManager.IsContainerHooksEnabled(ExecutionContext.Global.Variables))
|
|
||||||
{
|
|
||||||
containerHookManager = HostContext.GetService<IContainerHookManager>();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
dockerManager = HostContext.GetService<IDockerCommandManager>();
|
|
||||||
}
|
|
||||||
|
|
||||||
string dockerFile = null;
|
string dockerFile = null;
|
||||||
|
|
||||||
// container image haven't built/pull
|
// container image haven't built/pull
|
||||||
|
|||||||
@@ -306,13 +306,13 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
actionRunner.EvaluateDisplayName(contextData, context, out _);
|
actionRunner.TryEvaluateDisplayName(contextData, context);
|
||||||
jobSteps.Add(actionRunner);
|
jobSteps.Add(actionRunner);
|
||||||
|
|
||||||
if (prepareResult.PreStepTracker.TryGetValue(step.Id, out var preStep))
|
if (prepareResult.PreStepTracker.TryGetValue(step.Id, out var preStep))
|
||||||
{
|
{
|
||||||
Trace.Info($"Adding pre-{action.DisplayName}.");
|
Trace.Info($"Adding pre-{action.DisplayName}.");
|
||||||
preStep.EvaluateDisplayName(contextData, context, out _);
|
preStep.TryEvaluateDisplayName(contextData, context);
|
||||||
preStep.DisplayName = $"Pre {preStep.DisplayName}";
|
preStep.DisplayName = $"Pre {preStep.DisplayName}";
|
||||||
preJobSteps.Add(preStep);
|
preJobSteps.Add(preStep);
|
||||||
}
|
}
|
||||||
@@ -321,28 +321,25 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
if (message.Variables.TryGetValue("system.workflowFileFullPath", out VariableValue workflowFileFullPath))
|
if (message.Variables.TryGetValue("system.workflowFileFullPath", out VariableValue workflowFileFullPath))
|
||||||
{
|
{
|
||||||
var usesLogText = $"Uses: {workflowFileFullPath.Value}";
|
context.Output($"Uses: {workflowFileFullPath.Value}");
|
||||||
var reference = GetWorkflowReference(message.Variables);
|
|
||||||
context.Output(usesLogText + reference);
|
|
||||||
|
|
||||||
if (message.ContextData.TryGetValue("inputs", out var pipelineContextData))
|
if (message.ContextData.TryGetValue("inputs", out var pipelineContextData))
|
||||||
{
|
{
|
||||||
var inputs = pipelineContextData.AssertDictionary("inputs");
|
var inputs = pipelineContextData.AssertDictionary("inputs");
|
||||||
if (inputs.Any())
|
if (inputs.Any())
|
||||||
{
|
{
|
||||||
context.Output($"##[group] Inputs");
|
context.Output($"##[group] Inputs");
|
||||||
foreach (var input in inputs)
|
foreach (var input in inputs)
|
||||||
{
|
{
|
||||||
context.Output($" {input.Key}: {input.Value}");
|
context.Output($" {input.Key}: {input.Value}");
|
||||||
}
|
}
|
||||||
context.Output("##[endgroup]");
|
context.Output("##[endgroup]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(message.JobDisplayName))
|
if (!string.IsNullOrWhiteSpace(message.JobDisplayName))
|
||||||
{
|
{
|
||||||
context.Output($"Complete job name: {message.JobDisplayName}");
|
context.Output($"Complete job name: {message.JobDisplayName}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var intraActionStates = new Dictionary<Guid, Dictionary<string, string>>();
|
var intraActionStates = new Dictionary<Guid, Dictionary<string, string>>();
|
||||||
@@ -455,24 +452,6 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetWorkflowReference(IDictionary<string, VariableValue> variables)
|
|
||||||
{
|
|
||||||
var reference = "";
|
|
||||||
if (variables.TryGetValue("system.workflowFileSha", out VariableValue workflowFileSha))
|
|
||||||
{
|
|
||||||
if (variables.TryGetValue("system.workflowFileRef", out VariableValue workflowFileRef)
|
|
||||||
&& !string.IsNullOrEmpty(workflowFileRef.Value))
|
|
||||||
{
|
|
||||||
reference += $"@{workflowFileRef.Value} ({workflowFileSha.Value})";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
reference += $"@{workflowFileSha.Value}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return reference;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FinalizeJob(IExecutionContext jobContext, Pipelines.AgentJobRequestMessage message, DateTime jobStartTimeUtc)
|
public void FinalizeJob(IExecutionContext jobContext, Pipelines.AgentJobRequestMessage message, DateTime jobStartTimeUtc)
|
||||||
{
|
{
|
||||||
Trace.Entering();
|
Trace.Entering();
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Worker
|
namespace GitHub.Runner.Worker
|
||||||
{
|
{
|
||||||
@@ -33,18 +32,5 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
await _runAsync(ExecutionContext, _data);
|
await _runAsync(ExecutionContext, _data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryUpdateDisplayName(out bool updated)
|
|
||||||
{
|
|
||||||
updated = false;
|
|
||||||
return !string.IsNullOrEmpty(this.DisplayName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool EvaluateDisplayName(DictionaryContextData contextData, IExecutionContext context, out bool updated)
|
|
||||||
{
|
|
||||||
updated = false;
|
|
||||||
return !string.IsNullOrEmpty(this.DisplayName);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ using System.Net.Http;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.Pipelines;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
using GitHub.Runner.Common.Util;
|
using GitHub.Runner.Common.Util;
|
||||||
@@ -20,7 +20,7 @@ namespace GitHub.Runner.Worker
|
|||||||
[ServiceLocator(Default = typeof(JobRunner))]
|
[ServiceLocator(Default = typeof(JobRunner))]
|
||||||
public interface IJobRunner : IRunnerService
|
public interface IJobRunner : IRunnerService
|
||||||
{
|
{
|
||||||
Task<TaskResult> RunAsync(AgentJobRequestMessage message, CancellationToken jobRequestCancellationToken);
|
Task<TaskResult> RunAsync(Pipelines.AgentJobRequestMessage message, CancellationToken jobRequestCancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class JobRunner : RunnerService, IJobRunner
|
public sealed class JobRunner : RunnerService, IJobRunner
|
||||||
@@ -29,7 +29,7 @@ namespace GitHub.Runner.Worker
|
|||||||
private RunnerSettings _runnerSettings;
|
private RunnerSettings _runnerSettings;
|
||||||
private ITempDirectoryManager _tempDirectoryManager;
|
private ITempDirectoryManager _tempDirectoryManager;
|
||||||
|
|
||||||
public async Task<TaskResult> RunAsync(AgentJobRequestMessage message, CancellationToken jobRequestCancellationToken)
|
public async Task<TaskResult> RunAsync(Pipelines.AgentJobRequestMessage message, CancellationToken jobRequestCancellationToken)
|
||||||
{
|
{
|
||||||
// Validate parameters.
|
// Validate parameters.
|
||||||
Trace.Entering();
|
Trace.Entering();
|
||||||
@@ -40,34 +40,21 @@ namespace GitHub.Runner.Worker
|
|||||||
Trace.Info("Job ID {0}", message.JobId);
|
Trace.Info("Job ID {0}", message.JobId);
|
||||||
|
|
||||||
DateTime jobStartTimeUtc = DateTime.UtcNow;
|
DateTime jobStartTimeUtc = DateTime.UtcNow;
|
||||||
IRunnerService server = null;
|
|
||||||
|
|
||||||
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))
|
|
||||||
{
|
|
||||||
var runServer = HostContext.GetService<IRunServer>();
|
|
||||||
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
|
|
||||||
await runServer.ConnectAsync(systemConnection.Url, jobServerCredential);
|
|
||||||
server = runServer;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Setup the job server and job server queue.
|
|
||||||
var jobServer = HostContext.GetService<IJobServer>();
|
|
||||||
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
|
|
||||||
Uri jobServerUrl = systemConnection.Url;
|
|
||||||
|
|
||||||
Trace.Info($"Creating job server with URL: {jobServerUrl}");
|
// Setup the job server and job server queue.
|
||||||
// jobServerQueue is the throttling reporter.
|
var jobServer = HostContext.GetService<IJobServer>();
|
||||||
_jobServerQueue = HostContext.GetService<IJobServerQueue>();
|
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
|
||||||
VssConnection jobConnection = VssUtil.CreateConnection(jobServerUrl, jobServerCredential, new DelegatingHandler[] { new ThrottlingReportHandler(_jobServerQueue) });
|
Uri jobServerUrl = systemConnection.Url;
|
||||||
await jobServer.ConnectAsync(jobConnection);
|
|
||||||
|
|
||||||
_jobServerQueue.Start(message);
|
|
||||||
server = jobServer;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Trace.Info($"Creating job server with URL: {jobServerUrl}");
|
||||||
|
// jobServerQueue is the throttling reporter.
|
||||||
|
_jobServerQueue = HostContext.GetService<IJobServerQueue>();
|
||||||
|
VssConnection jobConnection = VssUtil.CreateConnection(jobServerUrl, jobServerCredential, new DelegatingHandler[] { new ThrottlingReportHandler(_jobServerQueue) });
|
||||||
|
await jobServer.ConnectAsync(jobConnection);
|
||||||
|
|
||||||
|
_jobServerQueue.Start(message);
|
||||||
HostContext.WritePerfCounter($"WorkerJobServerQueueStarted_{message.RequestId.ToString()}");
|
HostContext.WritePerfCounter($"WorkerJobServerQueueStarted_{message.RequestId.ToString()}");
|
||||||
|
|
||||||
IExecutionContext jobContext = null;
|
IExecutionContext jobContext = null;
|
||||||
@@ -112,7 +99,7 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
jobContext.Error(ex);
|
jobContext.Error(ex);
|
||||||
return await CompleteJobAsync(server, jobContext, message, TaskResult.Failed);
|
return await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jobContext.Global.WriteDebug)
|
if (jobContext.Global.WriteDebug)
|
||||||
@@ -149,7 +136,7 @@ namespace GitHub.Runner.Worker
|
|||||||
// don't log error issue to job ExecutionContext, since server owns the job level issue
|
// don't log error issue to job ExecutionContext, since server owns the job level issue
|
||||||
Trace.Error($"Job is cancelled during initialize.");
|
Trace.Error($"Job is cancelled during initialize.");
|
||||||
Trace.Error($"Caught exception: {ex}");
|
Trace.Error($"Caught exception: {ex}");
|
||||||
return await CompleteJobAsync(server, jobContext, message, TaskResult.Canceled);
|
return await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Canceled);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -157,7 +144,7 @@ namespace GitHub.Runner.Worker
|
|||||||
// don't log error issue to job ExecutionContext, since server owns the job level issue
|
// don't log error issue to job ExecutionContext, since server owns the job level issue
|
||||||
Trace.Error($"Job initialize failed.");
|
Trace.Error($"Job initialize failed.");
|
||||||
Trace.Error($"Caught exception from {nameof(jobExtension.InitializeJob)}: {ex}");
|
Trace.Error($"Caught exception from {nameof(jobExtension.InitializeJob)}: {ex}");
|
||||||
return await CompleteJobAsync(server, jobContext, message, TaskResult.Failed);
|
return await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// trace out all steps
|
// trace out all steps
|
||||||
@@ -194,7 +181,7 @@ namespace GitHub.Runner.Worker
|
|||||||
// Log the error and fail the job.
|
// Log the error and fail the job.
|
||||||
Trace.Error($"Caught exception from job steps {nameof(StepsRunner)}: {ex}");
|
Trace.Error($"Caught exception from job steps {nameof(StepsRunner)}: {ex}");
|
||||||
jobContext.Error(ex);
|
jobContext.Error(ex);
|
||||||
return await CompleteJobAsync(server, jobContext, message, TaskResult.Failed);
|
return await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -205,7 +192,7 @@ namespace GitHub.Runner.Worker
|
|||||||
Trace.Info($"Job result after all job steps finish: {jobContext.Result ?? TaskResult.Succeeded}");
|
Trace.Info($"Job result after all job steps finish: {jobContext.Result ?? TaskResult.Succeeded}");
|
||||||
|
|
||||||
Trace.Info("Completing the job execution context.");
|
Trace.Info("Completing the job execution context.");
|
||||||
return await CompleteJobAsync(server, jobContext, message);
|
return await CompleteJobAsync(jobServer, jobContext, message);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -219,66 +206,6 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<TaskResult> CompleteJobAsync(IRunnerService server, IExecutionContext jobContext, Pipelines.AgentJobRequestMessage message, TaskResult? taskResult = null)
|
|
||||||
{
|
|
||||||
if (server is IRunServer runServer)
|
|
||||||
{
|
|
||||||
return await CompleteJobAsync(runServer, jobContext, message, taskResult);
|
|
||||||
}
|
|
||||||
else if (server is IJobServer jobServer)
|
|
||||||
{
|
|
||||||
return await CompleteJobAsync(jobServer, jobContext, message, taskResult);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<TaskResult> CompleteJobAsync(IRunServer runServer, IExecutionContext jobContext, Pipelines.AgentJobRequestMessage message, TaskResult? taskResult = null)
|
|
||||||
{
|
|
||||||
jobContext.Debug($"Finishing: {message.JobDisplayName}");
|
|
||||||
TaskResult result = jobContext.Complete(taskResult);
|
|
||||||
if (jobContext.Global.Variables.TryGetValue("Node12ActionsWarnings", out var node12Warnings))
|
|
||||||
{
|
|
||||||
var actions = string.Join(", ", StringUtil.ConvertFromJson<HashSet<string>>(node12Warnings));
|
|
||||||
jobContext.Warning(string.Format(Constants.Runner.Node12DetectedAfterEndOfLife, actions));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure to clean temp after file upload since they may be pending fileupload still use the TEMP dir.
|
|
||||||
_tempDirectoryManager?.CleanupTempDirectory();
|
|
||||||
|
|
||||||
// Load any upgrade telemetry
|
|
||||||
LoadFromTelemetryFile(jobContext.Global.JobTelemetry);
|
|
||||||
|
|
||||||
// Make sure we don't submit secrets as telemetry
|
|
||||||
MaskTelemetrySecrets(jobContext.Global.JobTelemetry);
|
|
||||||
|
|
||||||
Trace.Info($"Raising job completed against run service");
|
|
||||||
var completeJobRetryLimit = 5;
|
|
||||||
var exceptions = new List<Exception>();
|
|
||||||
while (completeJobRetryLimit-- > 0)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, result, jobContext.JobOutputs, jobContext.Global.StepsResult, default);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Trace.Error($"Catch exception while attempting to complete job {message.JobId}, job request {message.RequestId}.");
|
|
||||||
Trace.Error(ex);
|
|
||||||
exceptions.Add(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// delay 5 seconds before next retry.
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
|
||||||
}
|
|
||||||
|
|
||||||
// rethrow exceptions from all attempts.
|
|
||||||
throw new AggregateException(exceptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<TaskResult> CompleteJobAsync(IJobServer jobServer, IExecutionContext jobContext, Pipelines.AgentJobRequestMessage message, TaskResult? taskResult = null)
|
private async Task<TaskResult> CompleteJobAsync(IJobServer jobServer, IExecutionContext jobContext, Pipelines.AgentJobRequestMessage message, TaskResult? taskResult = null)
|
||||||
{
|
{
|
||||||
jobContext.Debug($"Finishing: {message.JobDisplayName}");
|
jobContext.Debug($"Finishing: {message.JobDisplayName}");
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.Expressions2;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
|
using GitHub.DistributedTask.Pipelines;
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
@@ -11,6 +14,8 @@ using GitHub.Runner.Common;
|
|||||||
using GitHub.Runner.Common.Util;
|
using GitHub.Runner.Common.Util;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
using GitHub.Runner.Worker.Expressions;
|
using GitHub.Runner.Worker.Expressions;
|
||||||
|
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
|
||||||
|
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||||
|
|
||||||
namespace GitHub.Runner.Worker
|
namespace GitHub.Runner.Worker
|
||||||
{
|
{
|
||||||
@@ -21,8 +26,6 @@ namespace GitHub.Runner.Worker
|
|||||||
string DisplayName { get; set; }
|
string DisplayName { get; set; }
|
||||||
IExecutionContext ExecutionContext { get; set; }
|
IExecutionContext ExecutionContext { get; set; }
|
||||||
TemplateToken Timeout { get; }
|
TemplateToken Timeout { get; }
|
||||||
bool TryUpdateDisplayName(out bool updated);
|
|
||||||
bool EvaluateDisplayName(DictionaryContextData contextData, IExecutionContext context, out bool updated);
|
|
||||||
Task RunAsync();
|
Task RunAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,12 +195,6 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// This is our last, best chance to expand the display name. (At this point, all the requirements for successful expansion should be met.)
|
|
||||||
// That being said, evaluating the display name should still be considered as a "best effort" exercise. (It's not critical or paramount.)
|
|
||||||
// For that reason, we call a safe "Try..." wrapper method to ensure that any potential problems we encounter in evaluating the display name
|
|
||||||
// don't interfere with our ultimate goal within this code block: evaluation of the condition.
|
|
||||||
step.TryUpdateDisplayName(out _);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(conditionTraceWriter);
|
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(conditionTraceWriter);
|
||||||
@@ -259,6 +256,14 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
private async Task RunStepAsync(IStep step, CancellationToken jobCancellationToken)
|
private async Task RunStepAsync(IStep step, CancellationToken jobCancellationToken)
|
||||||
{
|
{
|
||||||
|
// Check to see if we can expand the display name
|
||||||
|
if (step is IActionRunner actionRunner &&
|
||||||
|
actionRunner.Stage == ActionRunStage.Main &&
|
||||||
|
actionRunner.TryEvaluateDisplayName(step.ExecutionContext.ExpressionValues, step.ExecutionContext))
|
||||||
|
{
|
||||||
|
step.ExecutionContext.UpdateTimelineRecordDisplayName(actionRunner.DisplayName);
|
||||||
|
}
|
||||||
|
|
||||||
// Start the step
|
// Start the step
|
||||||
Trace.Info("Starting the step.");
|
Trace.Info("Starting the step.");
|
||||||
step.ExecutionContext.Debug($"Starting: {step.DisplayName}");
|
step.ExecutionContext.Debug($"Starting: {step.DisplayName}");
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using GitHub.Services.OAuth;
|
||||||
|
|
||||||
|
namespace GitHub.Services.Common
|
||||||
|
{
|
||||||
|
public static class VssCredentialsExtension
|
||||||
|
{
|
||||||
|
public static VssOAuthCredential ToOAuthCredentials(
|
||||||
|
this VssCredentials credentials)
|
||||||
|
{
|
||||||
|
if (credentials.Federated.CredentialType == VssCredentialsType.OAuth)
|
||||||
|
{
|
||||||
|
return credentials.Federated as VssOAuthCredential;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,20 +12,20 @@ namespace GitHub.Services.Common
|
|||||||
public class RawHttpMessageHandler: HttpMessageHandler
|
public class RawHttpMessageHandler: HttpMessageHandler
|
||||||
{
|
{
|
||||||
public RawHttpMessageHandler(
|
public RawHttpMessageHandler(
|
||||||
FederatedCredential credentials)
|
VssOAuthCredential credentials)
|
||||||
: this(credentials, new RawClientHttpRequestSettings())
|
: this(credentials, new RawClientHttpRequestSettings())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public RawHttpMessageHandler(
|
public RawHttpMessageHandler(
|
||||||
FederatedCredential credentials,
|
VssOAuthCredential credentials,
|
||||||
RawClientHttpRequestSettings settings)
|
RawClientHttpRequestSettings settings)
|
||||||
: this(credentials, settings, new HttpClientHandler())
|
: this(credentials, settings, new HttpClientHandler())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public RawHttpMessageHandler(
|
public RawHttpMessageHandler(
|
||||||
FederatedCredential credentials,
|
VssOAuthCredential credentials,
|
||||||
RawClientHttpRequestSettings settings,
|
RawClientHttpRequestSettings settings,
|
||||||
HttpMessageHandler innerHandler)
|
HttpMessageHandler innerHandler)
|
||||||
{
|
{
|
||||||
@@ -56,7 +56,7 @@ namespace GitHub.Services.Common
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the credentials associated with this handler.
|
/// Gets the credentials associated with this handler.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public FederatedCredential Credentials
|
public VssOAuthCredential Credentials
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
private set;
|
private set;
|
||||||
@@ -111,7 +111,7 @@ namespace GitHub.Services.Common
|
|||||||
// 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)
|
if (m_tokenProvider == null)
|
||||||
{
|
{
|
||||||
m_tokenProvider = this.Credentials.CreateTokenProvider(request.RequestUri, null, null);
|
m_tokenProvider = this.Credentials.GetTokenProvider(request.RequestUri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,7 +254,7 @@ namespace GitHub.Services.Common
|
|||||||
private CredentialWrapper m_credentialWrapper;
|
private CredentialWrapper m_credentialWrapper;
|
||||||
private object m_thisLock;
|
private object m_thisLock;
|
||||||
private const Int32 m_maxAuthRetries = 3;
|
private const Int32 m_maxAuthRetries = 3;
|
||||||
private IssuedTokenProvider m_tokenProvider;
|
private VssOAuthTokenProvider m_tokenProvider;
|
||||||
|
|
||||||
//.Net Core does not attempt NTLM schema on Linux, unless ICredentials is a CredentialCache instance
|
//.Net Core does not attempt NTLM schema on Linux, unless ICredentials is a CredentialCache instance
|
||||||
//This workaround may not be needed after this corefx fix is consumed: https://github.com/dotnet/corefx/pull/7923
|
//This workaround may not be needed after this corefx fix is consumed: https://github.com/dotnet/corefx/pull/7923
|
||||||
|
|||||||
@@ -42,10 +42,9 @@ namespace GitHub.DistributedTask.Pipelines
|
|||||||
IList<String> fileTable,
|
IList<String> fileTable,
|
||||||
TemplateToken jobOutputs,
|
TemplateToken jobOutputs,
|
||||||
IList<TemplateToken> defaults,
|
IList<TemplateToken> defaults,
|
||||||
ActionsEnvironmentReference actionsEnvironment,
|
ActionsEnvironmentReference actionsEnvironment)
|
||||||
String messageType = JobRequestMessageTypes.PipelineAgentJobRequest)
|
|
||||||
{
|
{
|
||||||
this.MessageType = messageType;
|
this.MessageType = JobRequestMessageTypes.PipelineAgentJobRequest;
|
||||||
this.Plan = plan;
|
this.Plan = plan;
|
||||||
this.JobId = jobId;
|
this.JobId = jobId;
|
||||||
this.JobDisplayName = jobDisplayName;
|
this.JobDisplayName = jobDisplayName;
|
||||||
|
|||||||
@@ -455,6 +455,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
private readonly String[] s_expressionValueNames = new[]
|
private readonly String[] s_expressionValueNames = new[]
|
||||||
{
|
{
|
||||||
PipelineTemplateConstants.GitHub,
|
PipelineTemplateConstants.GitHub,
|
||||||
|
PipelineTemplateConstants.Needs,
|
||||||
PipelineTemplateConstants.Strategy,
|
PipelineTemplateConstants.Strategy,
|
||||||
PipelineTemplateConstants.Matrix,
|
PipelineTemplateConstants.Matrix,
|
||||||
PipelineTemplateConstants.Needs,
|
PipelineTemplateConstants.Needs,
|
||||||
|
|||||||
@@ -222,9 +222,6 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"job-outputs": {
|
"job-outputs": {
|
||||||
"context": [
|
|
||||||
"matrix"
|
|
||||||
],
|
|
||||||
"mapping": {
|
"mapping": {
|
||||||
"loose-key-type": "non-empty-string",
|
"loose-key-type": "non-empty-string",
|
||||||
"loose-value-type": "string-runner-context"
|
"loose-value-type": "string-runner-context"
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
using GitHub.Services.WebApi;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.WebApi
|
|
||||||
{
|
|
||||||
|
|
||||||
public class ListRunnersResponse
|
|
||||||
{
|
|
||||||
public ListRunnersResponse()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public ListRunnersResponse(ListRunnersResponse responseToBeCloned)
|
|
||||||
{
|
|
||||||
this.TotalCount = responseToBeCloned.TotalCount;
|
|
||||||
this.Runners = responseToBeCloned.Runners;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonProperty("total_count")]
|
|
||||||
public int TotalCount
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonProperty("runners")]
|
|
||||||
public List<Runner> Runners
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ListRunnersResponse Clone()
|
|
||||||
{
|
|
||||||
return new ListRunnersResponse(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<TaskAgent> ToTaskAgents()
|
|
||||||
{
|
|
||||||
return Runners.Select(runner => new TaskAgent() { Name = runner.Name }).ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,28 +1,25 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.Pipelines;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
using GitHub.Services.Common;
|
using GitHub.Services.Common;
|
||||||
using GitHub.Services.OAuth;
|
using GitHub.Services.OAuth;
|
||||||
using GitHub.Services.WebApi;
|
using GitHub.Services.WebApi;
|
||||||
using Sdk.RSWebApi.Contracts;
|
|
||||||
using Sdk.WebApi.WebApi;
|
using Sdk.WebApi.WebApi;
|
||||||
|
|
||||||
namespace GitHub.Actions.RunService.WebApi
|
namespace GitHub.DistributedTask.WebApi
|
||||||
{
|
{
|
||||||
public class BrokerHttpClient : RawHttpClientBase
|
[ResourceArea(TaskResourceIds.AreaId)]
|
||||||
|
public class RunServiceHttpClient : RawHttpClientBase
|
||||||
{
|
{
|
||||||
public BrokerHttpClient(
|
public RunServiceHttpClient(
|
||||||
Uri baseUrl,
|
Uri baseUrl,
|
||||||
VssOAuthCredential credentials)
|
VssOAuthCredential credentials)
|
||||||
: base(baseUrl, credentials)
|
: base(baseUrl, credentials)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public BrokerHttpClient(
|
public RunServiceHttpClient(
|
||||||
Uri baseUrl,
|
Uri baseUrl,
|
||||||
VssOAuthCredential credentials,
|
VssOAuthCredential credentials,
|
||||||
RawClientHttpRequestSettings settings)
|
RawClientHttpRequestSettings settings)
|
||||||
@@ -30,7 +27,7 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public BrokerHttpClient(
|
public RunServiceHttpClient(
|
||||||
Uri baseUrl,
|
Uri baseUrl,
|
||||||
VssOAuthCredential credentials,
|
VssOAuthCredential credentials,
|
||||||
params DelegatingHandler[] handlers)
|
params DelegatingHandler[] handlers)
|
||||||
@@ -38,7 +35,7 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public BrokerHttpClient(
|
public RunServiceHttpClient(
|
||||||
Uri baseUrl,
|
Uri baseUrl,
|
||||||
VssOAuthCredential credentials,
|
VssOAuthCredential credentials,
|
||||||
RawClientHttpRequestSettings settings,
|
RawClientHttpRequestSettings settings,
|
||||||
@@ -47,7 +44,7 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public BrokerHttpClient(
|
public RunServiceHttpClient(
|
||||||
Uri baseUrl,
|
Uri baseUrl,
|
||||||
HttpMessageHandler pipeline,
|
HttpMessageHandler pipeline,
|
||||||
Boolean disposeHandler)
|
Boolean disposeHandler)
|
||||||
@@ -55,29 +52,23 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<TaskAgentMessage> GetRunnerMessageAsync(
|
public Task<Pipelines.AgentJobRequestMessage> GetJobMessageAsync(
|
||||||
string runnerVersion,
|
Uri requestUri,
|
||||||
TaskAgentStatus? status,
|
string messageId,
|
||||||
CancellationToken cancellationToken = default
|
CancellationToken cancellationToken = default)
|
||||||
)
|
|
||||||
{
|
{
|
||||||
var requestUri = new Uri(Client.BaseAddress, "message");
|
HttpMethod httpMethod = new HttpMethod("POST");
|
||||||
|
var payload = new {
|
||||||
|
StreamID = messageId
|
||||||
|
};
|
||||||
|
|
||||||
List<KeyValuePair<string, string>> queryParams = new List<KeyValuePair<string, string>>();
|
var payloadJson = JsonUtility.ToString(payload);
|
||||||
|
var requestContent = new StringContent(payloadJson, System.Text.Encoding.UTF8, "application/json");
|
||||||
if (status != null)
|
return SendAsync<Pipelines.AgentJobRequestMessage>(
|
||||||
{
|
httpMethod,
|
||||||
queryParams.Add("status", status.Value.ToString());
|
additionalHeaders: null,
|
||||||
}
|
|
||||||
if (runnerVersion != null)
|
|
||||||
{
|
|
||||||
queryParams.Add("runnerVersion", runnerVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
return SendAsync<TaskAgentMessage>(
|
|
||||||
new HttpMethod("GET"),
|
|
||||||
requestUri: requestUri,
|
requestUri: requestUri,
|
||||||
queryParameters: queryParams,
|
content: requestContent,
|
||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.WebApi
|
|
||||||
{
|
|
||||||
public class Runner
|
|
||||||
{
|
|
||||||
|
|
||||||
public class Authorization
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The url to refresh tokens
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("authorization_url")]
|
|
||||||
public Uri AuthorizationUrl
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
internal set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The url to connect to to poll for messages
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("server_url")]
|
|
||||||
public string ServerUrl
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
internal set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The client id to use when connecting to the authorization_url
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("client_id")]
|
|
||||||
public string ClientId
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
internal set;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonProperty("name")]
|
|
||||||
public string Name
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
internal set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonProperty("id")]
|
|
||||||
public Int32 Id
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
internal set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonProperty("authorization")]
|
|
||||||
public Authorization RunnerAuthorization
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
internal set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TaskAgent ApplyToTaskAgent(TaskAgent agent)
|
|
||||||
{
|
|
||||||
agent.Id = this.Id;
|
|
||||||
agent.Authorization = new TaskAgentAuthorization()
|
|
||||||
{
|
|
||||||
AuthorizationUrl = this.RunnerAuthorization.AuthorizationUrl,
|
|
||||||
ClientId = new Guid(this.RunnerAuthorization.ClientId)
|
|
||||||
};
|
|
||||||
return agent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
using GitHub.Services.WebApi;
|
|
||||||
using System;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.WebApi
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// An organization-level grouping of runners.
|
|
||||||
/// </summary>
|
|
||||||
[DataContract]
|
|
||||||
public class RunnerGroup
|
|
||||||
{
|
|
||||||
internal RunnerGroup()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public RunnerGroup(String name)
|
|
||||||
{
|
|
||||||
this.Name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
private RunnerGroup(RunnerGroup poolToBeCloned)
|
|
||||||
{
|
|
||||||
this.Id = poolToBeCloned.Id;
|
|
||||||
this.IsHosted = poolToBeCloned.IsHosted;
|
|
||||||
this.Name = poolToBeCloned.Name;
|
|
||||||
this.IsDefault = poolToBeCloned.IsDefault;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
[JsonProperty("id")]
|
|
||||||
public Int32 Id
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
|
||||||
[JsonProperty("name")]
|
|
||||||
public String Name
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not this pool is internal and can't be modified by users
|
|
||||||
/// </summary>
|
|
||||||
[DataMember]
|
|
||||||
[JsonProperty("default")]
|
|
||||||
public bool IsDefault
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not this pool is managed by the service.
|
|
||||||
/// </summary>
|
|
||||||
[DataMember]
|
|
||||||
[JsonProperty("is_hosted")]
|
|
||||||
public Boolean IsHosted
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class RunnerGroupList
|
|
||||||
{
|
|
||||||
public RunnerGroupList()
|
|
||||||
{
|
|
||||||
this.RunnerGroups = new List<RunnerGroup>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<TaskAgentPool> ToAgentPoolList()
|
|
||||||
{
|
|
||||||
var agentPools = this.RunnerGroups.Select(x => new TaskAgentPool(x.Name)
|
|
||||||
{
|
|
||||||
Id = x.Id,
|
|
||||||
IsHosted = x.IsHosted,
|
|
||||||
IsInternal = x.IsDefault
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
return agentPools;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonProperty("runner_groups")]
|
|
||||||
public List<RunnerGroup> RunnerGroups { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("total_count")]
|
|
||||||
public int Count { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using GitHub.Services.Common;
|
using GitHub.Services.Common;
|
||||||
using GitHub.Services.WebApi;
|
using GitHub.Services.WebApi;
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
@@ -100,7 +100,6 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
public static readonly String Summary = "DistributedTask.Core.Summary";
|
public static readonly String Summary = "DistributedTask.Core.Summary";
|
||||||
public static readonly String FileAttachment = "DistributedTask.Core.FileAttachment";
|
public static readonly String FileAttachment = "DistributedTask.Core.FileAttachment";
|
||||||
public static readonly String DiagnosticLog = "DistributedTask.Core.DiagnosticLog";
|
public static readonly String DiagnosticLog = "DistributedTask.Core.DiagnosticLog";
|
||||||
public static readonly String ResultsLog = "Results.Core.Log";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[GenerateAllConstants]
|
[GenerateAllConstants]
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
|
|
||||||
namespace GitHub.Actions.RunService.WebApi
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class AcquireJobRequest
|
|
||||||
{
|
|
||||||
[DataMember(Name = "streamId", EmitDefaultValue = false)]
|
|
||||||
public string StreamID { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
|
|
||||||
namespace GitHub.Actions.RunService.WebApi
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class CompleteJobRequest
|
|
||||||
{
|
|
||||||
[DataMember(Name = "planId", EmitDefaultValue = false)]
|
|
||||||
public Guid PlanID { get; set; }
|
|
||||||
|
|
||||||
[DataMember(Name = "jobId", EmitDefaultValue = false)]
|
|
||||||
public Guid JobID { get; set; }
|
|
||||||
|
|
||||||
[DataMember(Name = "conclusion")]
|
|
||||||
public TaskResult Conclusion { get; set; }
|
|
||||||
|
|
||||||
[DataMember(Name = "outputs", EmitDefaultValue = false)]
|
|
||||||
public Dictionary<string, VariableValue> Outputs { get; set; }
|
|
||||||
|
|
||||||
[DataMember(Name = "stepResults", EmitDefaultValue = false)]
|
|
||||||
public IList<StepResult> StepResults { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace GitHub.Actions.RunService.WebApi
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class RenewJobRequest
|
|
||||||
{
|
|
||||||
[DataMember(Name = "planId", EmitDefaultValue = false)]
|
|
||||||
public Guid PlanID { get; set; }
|
|
||||||
|
|
||||||
[DataMember(Name = "jobId", EmitDefaultValue = false)]
|
|
||||||
public Guid JobID { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace Sdk.RSWebApi.Contracts
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class RenewJobResponse
|
|
||||||
{
|
|
||||||
[DataMember]
|
|
||||||
public DateTime LockedUntil
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
internal set;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
|
|
||||||
namespace GitHub.Actions.RunService.WebApi
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public class StepResult
|
|
||||||
{
|
|
||||||
[DataMember(Name = "external_id", EmitDefaultValue = false)]
|
|
||||||
public Guid ExternalID { get; set; }
|
|
||||||
|
|
||||||
[DataMember(Name = "number", EmitDefaultValue = false)]
|
|
||||||
public int? Number { get; set; }
|
|
||||||
|
|
||||||
[DataMember(Name = "name", EmitDefaultValue = false)]
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
[DataMember(Name = "status")]
|
|
||||||
public TimelineRecordState? Status { get; set; }
|
|
||||||
|
|
||||||
[DataMember(Name = "conclusion")]
|
|
||||||
public TaskResult? Conclusion { get; set; }
|
|
||||||
|
|
||||||
[DataMember(Name = "started_at", EmitDefaultValue = false)]
|
|
||||||
public DateTime? StartedAt { get; set; }
|
|
||||||
|
|
||||||
[DataMember(Name = "completed_at", EmitDefaultValue = false)]
|
|
||||||
public DateTime? CompletedAt { get; set; }
|
|
||||||
|
|
||||||
[DataMember(Name = "completed_log_url", EmitDefaultValue = false)]
|
|
||||||
public string CompletedLogURL { get; set; }
|
|
||||||
|
|
||||||
[DataMember(Name = "completed_log_lines", EmitDefaultValue = false)]
|
|
||||||
public long? CompletedLogLines { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using GitHub.DistributedTask.Pipelines;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
using GitHub.Services.Common;
|
|
||||||
using GitHub.Services.OAuth;
|
|
||||||
using GitHub.Services.WebApi;
|
|
||||||
using Sdk.RSWebApi.Contracts;
|
|
||||||
using Sdk.WebApi.WebApi;
|
|
||||||
|
|
||||||
namespace GitHub.Actions.RunService.WebApi
|
|
||||||
{
|
|
||||||
public class RunServiceHttpClient : RawHttpClientBase
|
|
||||||
{
|
|
||||||
public RunServiceHttpClient(
|
|
||||||
Uri baseUrl,
|
|
||||||
VssOAuthCredential credentials)
|
|
||||||
: base(baseUrl, credentials)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public RunServiceHttpClient(
|
|
||||||
Uri baseUrl,
|
|
||||||
VssOAuthCredential credentials,
|
|
||||||
RawClientHttpRequestSettings settings)
|
|
||||||
: base(baseUrl, credentials, settings)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public RunServiceHttpClient(
|
|
||||||
Uri baseUrl,
|
|
||||||
VssOAuthCredential credentials,
|
|
||||||
params DelegatingHandler[] handlers)
|
|
||||||
: base(baseUrl, credentials, handlers)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public RunServiceHttpClient(
|
|
||||||
Uri baseUrl,
|
|
||||||
VssOAuthCredential credentials,
|
|
||||||
RawClientHttpRequestSettings settings,
|
|
||||||
params DelegatingHandler[] handlers)
|
|
||||||
: base(baseUrl, credentials, settings, handlers)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public RunServiceHttpClient(
|
|
||||||
Uri baseUrl,
|
|
||||||
HttpMessageHandler pipeline,
|
|
||||||
Boolean disposeHandler)
|
|
||||||
: base(baseUrl, pipeline, disposeHandler)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<AgentJobRequestMessage> GetJobMessageAsync(
|
|
||||||
Uri requestUri,
|
|
||||||
string messageId,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
HttpMethod httpMethod = new HttpMethod("POST");
|
|
||||||
var payload = new AcquireJobRequest
|
|
||||||
{
|
|
||||||
StreamID = messageId
|
|
||||||
};
|
|
||||||
|
|
||||||
requestUri = new Uri(requestUri, "acquirejob");
|
|
||||||
|
|
||||||
var requestContent = new ObjectContent<AcquireJobRequest>(payload, new VssJsonMediaTypeFormatter(true));
|
|
||||||
return SendAsync<AgentJobRequestMessage>(
|
|
||||||
httpMethod,
|
|
||||||
requestUri: requestUri,
|
|
||||||
content: requestContent,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task CompleteJobAsync(
|
|
||||||
Uri requestUri,
|
|
||||||
Guid planId,
|
|
||||||
Guid jobId,
|
|
||||||
TaskResult result,
|
|
||||||
Dictionary<String, VariableValue> outputs,
|
|
||||||
IList<StepResult> stepResults,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
HttpMethod httpMethod = new HttpMethod("POST");
|
|
||||||
var payload = new CompleteJobRequest()
|
|
||||||
{
|
|
||||||
PlanID = planId,
|
|
||||||
JobID = jobId,
|
|
||||||
Conclusion = result,
|
|
||||||
Outputs = outputs,
|
|
||||||
StepResults = stepResults
|
|
||||||
};
|
|
||||||
|
|
||||||
requestUri = new Uri(requestUri, "completejob");
|
|
||||||
|
|
||||||
var requestContent = new ObjectContent<CompleteJobRequest>(payload, new VssJsonMediaTypeFormatter(true));
|
|
||||||
return SendAsync(
|
|
||||||
httpMethod,
|
|
||||||
requestUri,
|
|
||||||
content: requestContent,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<RenewJobResponse> RenewJobAsync(
|
|
||||||
Uri requestUri,
|
|
||||||
Guid planId,
|
|
||||||
Guid jobId,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
HttpMethod httpMethod = new HttpMethod("POST");
|
|
||||||
var payload = new RenewJobRequest()
|
|
||||||
{
|
|
||||||
PlanID = planId,
|
|
||||||
JobID = jobId
|
|
||||||
};
|
|
||||||
|
|
||||||
requestUri = new Uri(requestUri, "renewjob");
|
|
||||||
|
|
||||||
var requestContent = new ObjectContent<RenewJobRequest>(payload, new VssJsonMediaTypeFormatter(true));
|
|
||||||
return SendAsync<RenewJobResponse>(
|
|
||||||
httpMethod,
|
|
||||||
requestUri,
|
|
||||||
content: requestContent,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -46,81 +46,7 @@ namespace GitHub.Services.Results.Contracts
|
|||||||
|
|
||||||
[DataContract]
|
[DataContract]
|
||||||
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
||||||
public class GetSignedJobLogsURLRequest
|
public class CreateStepSummaryMetadataResponse
|
||||||
{
|
|
||||||
[DataMember]
|
|
||||||
public string WorkflowJobRunBackendId;
|
|
||||||
[DataMember]
|
|
||||||
public string WorkflowRunBackendId;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataContract]
|
|
||||||
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
|
||||||
public class GetSignedJobLogsURLResponse
|
|
||||||
{
|
|
||||||
[DataMember]
|
|
||||||
public string LogsUrl;
|
|
||||||
[DataMember]
|
|
||||||
public string BlobStorageType;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataContract]
|
|
||||||
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
|
||||||
public class GetSignedStepLogsURLRequest
|
|
||||||
{
|
|
||||||
[DataMember]
|
|
||||||
public string WorkflowJobRunBackendId;
|
|
||||||
[DataMember]
|
|
||||||
public string WorkflowRunBackendId;
|
|
||||||
[DataMember]
|
|
||||||
public string StepBackendId;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataContract]
|
|
||||||
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
|
||||||
public class GetSignedStepLogsURLResponse
|
|
||||||
{
|
|
||||||
[DataMember]
|
|
||||||
public string LogsUrl;
|
|
||||||
[DataMember]
|
|
||||||
public string BlobStorageType;
|
|
||||||
[DataMember]
|
|
||||||
public long SoftSizeLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataContract]
|
|
||||||
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
|
||||||
public class JobLogsMetadataCreate
|
|
||||||
{
|
|
||||||
[DataMember]
|
|
||||||
public string WorkflowRunBackendId;
|
|
||||||
[DataMember]
|
|
||||||
public string WorkflowJobRunBackendId;
|
|
||||||
[DataMember]
|
|
||||||
public string UploadedAt;
|
|
||||||
[DataMember]
|
|
||||||
public long LineCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataContract]
|
|
||||||
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
|
||||||
public class StepLogsMetadataCreate
|
|
||||||
{
|
|
||||||
[DataMember]
|
|
||||||
public string WorkflowRunBackendId;
|
|
||||||
[DataMember]
|
|
||||||
public string WorkflowJobRunBackendId;
|
|
||||||
[DataMember]
|
|
||||||
public string StepBackendId;
|
|
||||||
[DataMember]
|
|
||||||
public string UploadedAt;
|
|
||||||
[DataMember]
|
|
||||||
public long LineCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataContract]
|
|
||||||
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
|
|
||||||
public class CreateMetadataResponse
|
|
||||||
{
|
{
|
||||||
[DataMember]
|
[DataMember]
|
||||||
public bool Ok;
|
public bool Ok;
|
||||||
|
|||||||
@@ -101,17 +101,6 @@ namespace Sdk.WebApi.WebApi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Task<T> SendAsync<T>(
|
|
||||||
HttpMethod method,
|
|
||||||
Uri requestUri,
|
|
||||||
HttpContent content = null,
|
|
||||||
IEnumerable<KeyValuePair<String, String>> queryParameters = null,
|
|
||||||
Object userState = null,
|
|
||||||
CancellationToken cancellationToken = default(CancellationToken))
|
|
||||||
{
|
|
||||||
return SendAsync<T>(method, null, requestUri, content, queryParameters, userState, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task<T> SendAsync<T>(
|
protected async Task<T> SendAsync<T>(
|
||||||
HttpMethod method,
|
HttpMethod method,
|
||||||
IEnumerable<KeyValuePair<String, String>> additionalHeaders,
|
IEnumerable<KeyValuePair<String, String>> additionalHeaders,
|
||||||
|
|||||||
@@ -24,138 +24,68 @@ namespace GitHub.Services.Results.Client
|
|||||||
m_formatter = new JsonMediaTypeFormatter();
|
m_formatter = new JsonMediaTypeFormatter();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get Sas URL calls
|
public async Task<GetSignedStepSummaryURLResponse> GetStepSummaryUploadUrlAsync(string planId, string jobId, string stepId, CancellationToken cancellationToken)
|
||||||
private async Task<T> GetResultsSignedURLResponse<R, T>(Uri uri, CancellationToken cancellationToken, R request)
|
|
||||||
{
|
{
|
||||||
using (HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, uri))
|
var request = new GetSignedStepSummaryURLRequest()
|
||||||
|
{
|
||||||
|
WorkflowJobRunBackendId= jobId,
|
||||||
|
WorkflowRunBackendId= planId,
|
||||||
|
StepBackendId= stepId
|
||||||
|
};
|
||||||
|
|
||||||
|
var stepSummaryUploadRequest = new Uri(m_resultsServiceUrl, "twirp/results.services.receiver.Receiver/GetStepSummarySignedBlobURL");
|
||||||
|
|
||||||
|
using (HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, stepSummaryUploadRequest))
|
||||||
{
|
{
|
||||||
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", m_token);
|
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", m_token);
|
||||||
requestMessage.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
|
requestMessage.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
|
||||||
|
|
||||||
using (HttpContent content = new ObjectContent<R>(request, m_formatter))
|
using (HttpContent content = new ObjectContent<GetSignedStepSummaryURLRequest>(request, m_formatter))
|
||||||
{
|
{
|
||||||
requestMessage.Content = content;
|
requestMessage.Content = content;
|
||||||
using (var response = await SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationToken: cancellationToken))
|
using (var response = await SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationToken: cancellationToken))
|
||||||
{
|
{
|
||||||
return await ReadJsonContentAsync<T>(response, cancellationToken);
|
return await ReadJsonContentAsync<GetSignedStepSummaryURLResponse>(response, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<GetSignedStepSummaryURLResponse> GetStepSummaryUploadUrlAsync(string planId, string jobId, Guid stepId, CancellationToken cancellationToken)
|
private async Task StepSummaryUploadCompleteAsync(string planId, string jobId, string stepId, long size, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var request = new GetSignedStepSummaryURLRequest()
|
var timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd'T'HH:mm:ss.fffK");
|
||||||
|
var request = new StepSummaryMetadataCreate()
|
||||||
{
|
{
|
||||||
WorkflowJobRunBackendId = jobId,
|
WorkflowJobRunBackendId= jobId,
|
||||||
WorkflowRunBackendId = planId,
|
WorkflowRunBackendId= planId,
|
||||||
StepBackendId = stepId.ToString()
|
StepBackendId = stepId,
|
||||||
|
Size = size,
|
||||||
|
UploadedAt = timestamp
|
||||||
};
|
};
|
||||||
|
|
||||||
var getStepSummarySignedBlobURLEndpoint = new Uri(m_resultsServiceUrl, Constants.GetStepSummarySignedBlobURL);
|
var stepSummaryUploadCompleteRequest = new Uri(m_resultsServiceUrl, "twirp/results.services.receiver.Receiver/CreateStepSummaryMetadata");
|
||||||
|
|
||||||
return await GetResultsSignedURLResponse<GetSignedStepSummaryURLRequest, GetSignedStepSummaryURLResponse>(getStepSummarySignedBlobURLEndpoint, cancellationToken, request);
|
using (HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, stepSummaryUploadCompleteRequest))
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<GetSignedStepLogsURLResponse> GetStepLogUploadUrlAsync(string planId, string jobId, Guid stepId, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var request = new GetSignedStepLogsURLRequest()
|
|
||||||
{
|
|
||||||
WorkflowJobRunBackendId = jobId,
|
|
||||||
WorkflowRunBackendId = planId,
|
|
||||||
StepBackendId = stepId.ToString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
var getStepLogsSignedBlobURLEndpoint = new Uri(m_resultsServiceUrl, Constants.GetStepLogsSignedBlobURL);
|
|
||||||
|
|
||||||
return await GetResultsSignedURLResponse<GetSignedStepLogsURLRequest, GetSignedStepLogsURLResponse>(getStepLogsSignedBlobURLEndpoint, cancellationToken, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<GetSignedJobLogsURLResponse> GetJobLogUploadUrlAsync(string planId, string jobId, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var request = new GetSignedJobLogsURLRequest()
|
|
||||||
{
|
|
||||||
WorkflowJobRunBackendId = jobId,
|
|
||||||
WorkflowRunBackendId = planId,
|
|
||||||
};
|
|
||||||
|
|
||||||
var getJobLogsSignedBlobURLEndpoint = new Uri(m_resultsServiceUrl, Constants.GetJobLogsSignedBlobURL);
|
|
||||||
|
|
||||||
return await GetResultsSignedURLResponse<GetSignedJobLogsURLRequest, GetSignedJobLogsURLResponse>(getJobLogsSignedBlobURLEndpoint, cancellationToken, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create metadata calls
|
|
||||||
|
|
||||||
private async Task CreateMetadata<R>(Uri uri, CancellationToken cancellationToken, R request, string timestamp)
|
|
||||||
{
|
|
||||||
using (HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, uri))
|
|
||||||
{
|
{
|
||||||
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", m_token);
|
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", m_token);
|
||||||
requestMessage.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
|
requestMessage.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
|
||||||
|
|
||||||
using (HttpContent content = new ObjectContent<R>(request, m_formatter))
|
using (HttpContent content = new ObjectContent<StepSummaryMetadataCreate>(request, m_formatter))
|
||||||
{
|
{
|
||||||
requestMessage.Content = content;
|
requestMessage.Content = content;
|
||||||
using (var response = await SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationToken: cancellationToken))
|
using (var response = await SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationToken: cancellationToken))
|
||||||
{
|
{
|
||||||
var jsonResponse = await ReadJsonContentAsync<CreateMetadataResponse>(response, cancellationToken);
|
var jsonResponse = await ReadJsonContentAsync<CreateStepSummaryMetadataResponse>(response, cancellationToken);
|
||||||
if (!jsonResponse.Ok)
|
if (!jsonResponse.Ok)
|
||||||
{
|
{
|
||||||
throw new Exception($"Failed to mark {typeof(R).Name} upload as complete, status code: {response.StatusCode}, ok: {jsonResponse.Ok}, timestamp: {timestamp}");
|
throw new Exception($"Failed to mark step summary upload as complete, status code: {response.StatusCode}, ok: {jsonResponse.Ok}, size: {size}, timestamp: {timestamp}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task StepSummaryUploadCompleteAsync(string planId, string jobId, Guid stepId, long size, CancellationToken cancellationToken)
|
private async Task<HttpResponseMessage> UploadFileAsync(string url, string blobStorageType, FileStream file, CancellationToken cancellationToken)
|
||||||
{
|
|
||||||
var timestamp = DateTime.UtcNow.ToString(Constants.TimestampFormat);
|
|
||||||
var request = new StepSummaryMetadataCreate()
|
|
||||||
{
|
|
||||||
WorkflowJobRunBackendId = jobId,
|
|
||||||
WorkflowRunBackendId = planId,
|
|
||||||
StepBackendId = stepId.ToString(),
|
|
||||||
Size = size,
|
|
||||||
UploadedAt = timestamp
|
|
||||||
};
|
|
||||||
|
|
||||||
var createStepSummaryMetadataEndpoint = new Uri(m_resultsServiceUrl, Constants.CreateStepSummaryMetadata);
|
|
||||||
await CreateMetadata<StepSummaryMetadataCreate>(createStepSummaryMetadataEndpoint, cancellationToken, request, timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task StepLogUploadCompleteAsync(string planId, string jobId, Guid stepId, long lineCount, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var timestamp = DateTime.UtcNow.ToString(Constants.TimestampFormat);
|
|
||||||
var request = new StepLogsMetadataCreate()
|
|
||||||
{
|
|
||||||
WorkflowJobRunBackendId = jobId,
|
|
||||||
WorkflowRunBackendId = planId,
|
|
||||||
StepBackendId = stepId.ToString(),
|
|
||||||
UploadedAt = timestamp,
|
|
||||||
LineCount = lineCount,
|
|
||||||
};
|
|
||||||
|
|
||||||
var createStepLogsMetadataEndpoint = new Uri(m_resultsServiceUrl, Constants.CreateStepLogsMetadata);
|
|
||||||
await CreateMetadata<StepLogsMetadataCreate>(createStepLogsMetadataEndpoint, cancellationToken, request, timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task JobLogUploadCompleteAsync(string planId, string jobId, long lineCount, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var timestamp = DateTime.UtcNow.ToString(Constants.TimestampFormat);
|
|
||||||
var request = new JobLogsMetadataCreate()
|
|
||||||
{
|
|
||||||
WorkflowJobRunBackendId = jobId,
|
|
||||||
WorkflowRunBackendId = planId,
|
|
||||||
UploadedAt = timestamp,
|
|
||||||
LineCount = lineCount,
|
|
||||||
};
|
|
||||||
|
|
||||||
var createJobLogsMetadataEndpoint = new Uri(m_resultsServiceUrl, Constants.CreateJobLogsMetadata);
|
|
||||||
await CreateMetadata<JobLogsMetadataCreate>(createJobLogsMetadataEndpoint, cancellationToken, request, timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<HttpResponseMessage> UploadBlockFileAsync(string url, string blobStorageType, FileStream file, CancellationToken cancellationToken)
|
|
||||||
{
|
{
|
||||||
// Upload the file to the url
|
// Upload the file to the url
|
||||||
var request = new HttpRequestMessage(HttpMethod.Put, url)
|
var request = new HttpRequestMessage(HttpMethod.Put, url)
|
||||||
@@ -165,7 +95,7 @@ namespace GitHub.Services.Results.Client
|
|||||||
|
|
||||||
if (blobStorageType == BlobStorageTypes.AzureBlobStorage)
|
if (blobStorageType == BlobStorageTypes.AzureBlobStorage)
|
||||||
{
|
{
|
||||||
request.Content.Headers.Add(Constants.AzureBlobTypeHeader, Constants.AzureBlockBlob);
|
request.Content.Headers.Add("x-ms-blob-type", "BlockBlob");
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var response = await SendAsync(request, HttpCompletionOption.ResponseHeadersRead, userState: null, cancellationToken))
|
using (var response = await SendAsync(request, HttpCompletionOption.ResponseHeadersRead, userState: null, cancellationToken))
|
||||||
@@ -178,62 +108,11 @@ namespace GitHub.Services.Results.Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<HttpResponseMessage> CreateAppendFileAsync(string url, string blobStorageType, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Put, url)
|
|
||||||
{
|
|
||||||
Content = new StringContent("")
|
|
||||||
};
|
|
||||||
if (blobStorageType == BlobStorageTypes.AzureBlobStorage)
|
|
||||||
{
|
|
||||||
request.Content.Headers.Add(Constants.AzureBlobTypeHeader, Constants.AzureAppendBlob);
|
|
||||||
request.Content.Headers.Add("Content-Length", "0");
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var response = await SendAsync(request, HttpCompletionOption.ResponseHeadersRead, userState: null, cancellationToken))
|
|
||||||
{
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
throw new Exception($"Failed to create append file, status code: {response.StatusCode}, reason: {response.ReasonPhrase}");
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<HttpResponseMessage> UploadAppendFileAsync(string url, string blobStorageType, FileStream file, bool finalize, long fileSize, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var comp = finalize ? "&comp=appendblock&seal=true" : "&comp=appendblock";
|
|
||||||
// Upload the file to the url
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Put, url + comp)
|
|
||||||
{
|
|
||||||
Content = new StreamContent(file)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (blobStorageType == BlobStorageTypes.AzureBlobStorage)
|
|
||||||
{
|
|
||||||
request.Content.Headers.Add("Content-Length", fileSize.ToString());
|
|
||||||
request.Content.Headers.Add(Constants.AzureBlobSealedHeader, finalize.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var response = await SendAsync(request, HttpCompletionOption.ResponseHeadersRead, userState: null, cancellationToken))
|
|
||||||
{
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
throw new Exception($"Failed to upload append file, status code: {response.StatusCode}, reason: {response.ReasonPhrase}, object: {response}, fileSize: {fileSize}");
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle file upload for step summary
|
// Handle file upload for step summary
|
||||||
public async Task UploadStepSummaryAsync(string planId, string jobId, Guid stepId, string file, CancellationToken cancellationToken)
|
public async Task UploadStepSummaryAsync(string planId, string jobId, string stepId, string file, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// Get the upload url
|
// Get the upload url
|
||||||
var uploadUrlResponse = await GetStepSummaryUploadUrlAsync(planId, jobId, stepId, cancellationToken);
|
var uploadUrlResponse = await GetStepSummaryUploadUrlAsync(planId, jobId, stepId, cancellationToken);
|
||||||
if (uploadUrlResponse == null)
|
|
||||||
{
|
|
||||||
throw new Exception("Failed to get step summary upload url");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do we want to throw an exception here or should we just be uploading/truncating the data
|
// Do we want to throw an exception here or should we just be uploading/truncating the data
|
||||||
var fileSize = new FileInfo(file).Length;
|
var fileSize = new FileInfo(file).Length;
|
||||||
@@ -245,97 +124,15 @@ namespace GitHub.Services.Results.Client
|
|||||||
// Upload the file
|
// Upload the file
|
||||||
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
|
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
|
||||||
{
|
{
|
||||||
var response = await UploadBlockFileAsync(uploadUrlResponse.SummaryUrl, uploadUrlResponse.BlobStorageType, fileStream, cancellationToken);
|
var response = await UploadFileAsync(uploadUrlResponse.SummaryUrl, uploadUrlResponse.BlobStorageType, fileStream, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send step summary upload complete message
|
// Send step summary upload complete message
|
||||||
await StepSummaryUploadCompleteAsync(planId, jobId, stepId, fileSize, cancellationToken);
|
await StepSummaryUploadCompleteAsync(planId, jobId, stepId, fileSize, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
|
||||||
{
|
|
||||||
// Get the upload url
|
|
||||||
var uploadUrlResponse = await GetStepLogUploadUrlAsync(planId, jobId, stepId, cancellationToken);
|
|
||||||
if (uploadUrlResponse == null || uploadUrlResponse.LogsUrl == null)
|
|
||||||
{
|
|
||||||
throw new Exception("Failed to get step log upload url");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
if (finalize)
|
|
||||||
{
|
|
||||||
// Send step log upload complete message
|
|
||||||
await StepLogUploadCompleteAsync(planId, jobId, stepId, lineCount, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle file upload for job log
|
|
||||||
public async Task UploadResultsJobLogAsync(string planId, string jobId, string file, bool finalize, bool firstBlock, long lineCount, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
// Get the upload url
|
|
||||||
var uploadUrlResponse = await GetJobLogUploadUrlAsync(planId, jobId, cancellationToken);
|
|
||||||
if (uploadUrlResponse == null || uploadUrlResponse.LogsUrl == null)
|
|
||||||
{
|
|
||||||
throw new Exception("Failed to get job log upload url");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
if (finalize)
|
|
||||||
{
|
|
||||||
// Send step log upload complete message
|
|
||||||
await JobLogUploadCompleteAsync(planId, jobId, lineCount, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private MediaTypeFormatter m_formatter;
|
private MediaTypeFormatter m_formatter;
|
||||||
private Uri m_resultsServiceUrl;
|
private Uri m_resultsServiceUrl;
|
||||||
private string m_token;
|
private string m_token;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constants specific to results
|
|
||||||
public static class Constants
|
|
||||||
{
|
|
||||||
public static readonly string TimestampFormat = "yyyy-MM-dd'T'HH:mm:ss.fffK";
|
|
||||||
|
|
||||||
public static readonly string ResultsReceiverTwirpEndpoint = "twirp/results.services.receiver.Receiver/";
|
|
||||||
public static readonly string GetStepSummarySignedBlobURL = ResultsReceiverTwirpEndpoint + "GetStepSummarySignedBlobURL";
|
|
||||||
public static readonly string CreateStepSummaryMetadata = ResultsReceiverTwirpEndpoint + "CreateStepSummaryMetadata";
|
|
||||||
public static readonly string GetStepLogsSignedBlobURL = ResultsReceiverTwirpEndpoint + "GetStepLogsSignedBlobURL";
|
|
||||||
public static readonly string CreateStepLogsMetadata = ResultsReceiverTwirpEndpoint + "CreateStepLogsMetadata";
|
|
||||||
public static readonly string GetJobLogsSignedBlobURL = ResultsReceiverTwirpEndpoint + "GetJobLogsSignedBlobURL";
|
|
||||||
public static readonly string CreateJobLogsMetadata = ResultsReceiverTwirpEndpoint + "CreateJobLogsMetadata";
|
|
||||||
|
|
||||||
public static readonly string AzureBlobSealedHeader = "x-ms-blob-sealed";
|
|
||||||
public static readonly string AzureBlobTypeHeader = "x-ms-blob-type";
|
|
||||||
public static readonly string AzureBlockBlob = "BlockBlob";
|
|
||||||
public static readonly string AzureAppendBlob = "AppendBlob";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -824,7 +824,6 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
[InlineData("remove", "version")]
|
[InlineData("remove", "version")]
|
||||||
[InlineData("remove", "commit")]
|
[InlineData("remove", "commit")]
|
||||||
[InlineData("remove", "check")]
|
[InlineData("remove", "check")]
|
||||||
[InlineData("remove", "local")]
|
|
||||||
[InlineData("run", "help")]
|
[InlineData("run", "help")]
|
||||||
[InlineData("run", "version")]
|
[InlineData("run", "version")]
|
||||||
[InlineData("run", "commit")]
|
[InlineData("run", "commit")]
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
public class ConfigurationManagerL0
|
public class ConfigurationManagerL0
|
||||||
{
|
{
|
||||||
private Mock<IRunnerServer> _runnerServer;
|
private Mock<IRunnerServer> _runnerServer;
|
||||||
private Mock<IRunnerDotcomServer> _dotcomServer;
|
|
||||||
private Mock<ILocationServer> _locationServer;
|
private Mock<ILocationServer> _locationServer;
|
||||||
private Mock<ICredentialManager> _credMgr;
|
private Mock<ICredentialManager> _credMgr;
|
||||||
private Mock<IPromptManager> _promptManager;
|
private Mock<IPromptManager> _promptManager;
|
||||||
@@ -56,7 +55,6 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
_store = new Mock<IConfigurationStore>();
|
_store = new Mock<IConfigurationStore>();
|
||||||
_extnMgr = new Mock<IExtensionManager>();
|
_extnMgr = new Mock<IExtensionManager>();
|
||||||
_rsaKeyManager = new Mock<IRSAKeyManager>();
|
_rsaKeyManager = new Mock<IRSAKeyManager>();
|
||||||
_dotcomServer = new Mock<IRunnerDotcomServer>();
|
|
||||||
|
|
||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
_serviceControlManager = new Mock<IWindowsServiceControlManager>();
|
_serviceControlManager = new Mock<IWindowsServiceControlManager>();
|
||||||
@@ -108,10 +106,6 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
_runnerServer.Setup(x => x.AddAgentAsync(It.IsAny<int>(), It.IsAny<TaskAgent>())).Returns(Task.FromResult(expectedAgent));
|
_runnerServer.Setup(x => x.AddAgentAsync(It.IsAny<int>(), It.IsAny<TaskAgent>())).Returns(Task.FromResult(expectedAgent));
|
||||||
_runnerServer.Setup(x => x.ReplaceAgentAsync(It.IsAny<int>(), It.IsAny<TaskAgent>())).Returns(Task.FromResult(expectedAgent));
|
_runnerServer.Setup(x => x.ReplaceAgentAsync(It.IsAny<int>(), It.IsAny<TaskAgent>())).Returns(Task.FromResult(expectedAgent));
|
||||||
|
|
||||||
_dotcomServer.Setup(x => x.GetRunnersAsync(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns(Task.FromResult(expectedAgents));
|
|
||||||
_dotcomServer.Setup(x => x.GetRunnerGroupsAsync(It.IsAny<string>(), It.IsAny<string>())).Returns(Task.FromResult(expectedPools));
|
|
||||||
_dotcomServer.Setup(x => x.AddRunnerAsync(It.IsAny<int>(), It.IsAny<TaskAgent>(), It.IsAny<string>(), It.IsAny<string>())).Returns(Task.FromResult(expectedAgent));
|
|
||||||
|
|
||||||
rsa = new RSACryptoServiceProvider(2048);
|
rsa = new RSACryptoServiceProvider(2048);
|
||||||
|
|
||||||
_rsaKeyManager.Setup(x => x.CreateKey()).Returns(rsa);
|
_rsaKeyManager.Setup(x => x.CreateKey()).Returns(rsa);
|
||||||
@@ -125,7 +119,6 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
tc.SetSingleton<IConfigurationStore>(_store.Object);
|
tc.SetSingleton<IConfigurationStore>(_store.Object);
|
||||||
tc.SetSingleton<IExtensionManager>(_extnMgr.Object);
|
tc.SetSingleton<IExtensionManager>(_extnMgr.Object);
|
||||||
tc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
tc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
tc.SetSingleton<IRunnerDotcomServer>(_dotcomServer.Object);
|
|
||||||
tc.SetSingleton<ILocationServer>(_locationServer.Object);
|
tc.SetSingleton<ILocationServer>(_locationServer.Object);
|
||||||
|
|
||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
|
||||||
using GitHub.DistributedTask.Pipelines;
|
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Listener;
|
using GitHub.Runner.Listener;
|
||||||
using GitHub.Runner.Listener.Configuration;
|
|
||||||
using GitHub.Services.WebApi;
|
using GitHub.Services.WebApi;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Sdk.RSWebApi.Contracts;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||||
@@ -23,8 +18,6 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
private Mock<IProcessChannel> _processChannel;
|
private Mock<IProcessChannel> _processChannel;
|
||||||
private Mock<IProcessInvoker> _processInvoker;
|
private Mock<IProcessInvoker> _processInvoker;
|
||||||
private Mock<IRunnerServer> _runnerServer;
|
private Mock<IRunnerServer> _runnerServer;
|
||||||
|
|
||||||
private Mock<IRunServer> _runServer;
|
|
||||||
private Mock<IConfigurationStore> _configurationStore;
|
private Mock<IConfigurationStore> _configurationStore;
|
||||||
|
|
||||||
public JobDispatcherL0()
|
public JobDispatcherL0()
|
||||||
@@ -32,7 +25,6 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_processChannel = new Mock<IProcessChannel>();
|
_processChannel = new Mock<IProcessChannel>();
|
||||||
_processInvoker = new Mock<IProcessInvoker>();
|
_processInvoker = new Mock<IProcessInvoker>();
|
||||||
_runnerServer = new Mock<IRunnerServer>();
|
_runnerServer = new Mock<IRunnerServer>();
|
||||||
_runServer = new Mock<IRunServer>();
|
|
||||||
_configurationStore = new Mock<IConfigurationStore>();
|
_configurationStore = new Mock<IConfigurationStore>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +139,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
var jobDispatcher = new JobDispatcher();
|
var jobDispatcher = new JobDispatcher();
|
||||||
jobDispatcher.Initialize(hc);
|
jobDispatcher.Initialize(hc);
|
||||||
|
|
||||||
await jobDispatcher.RenewJobRequestAsync(It.IsAny<AgentJobRequestMessage>(), It.IsAny<ServiceEndpoint>(), poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
||||||
|
|
||||||
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully);
|
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully);
|
||||||
_runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Exactly(5));
|
_runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Exactly(5));
|
||||||
@@ -205,7 +197,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
var jobDispatcher = new JobDispatcher();
|
var jobDispatcher = new JobDispatcher();
|
||||||
jobDispatcher.Initialize(hc);
|
jobDispatcher.Initialize(hc);
|
||||||
|
|
||||||
await jobDispatcher.RenewJobRequestAsync(It.IsAny<AgentJobRequestMessage>(), It.IsAny<ServiceEndpoint>(), poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
||||||
|
|
||||||
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
|
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
|
||||||
Assert.False(cancellationTokenSource.IsCancellationRequested);
|
Assert.False(cancellationTokenSource.IsCancellationRequested);
|
||||||
@@ -213,75 +205,6 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Runner")]
|
|
||||||
public async void DispatcherRenewJobOnRunServiceStopOnJobNotFoundExceptions()
|
|
||||||
{
|
|
||||||
//Arrange
|
|
||||||
using (var hc = new TestHostContext(this))
|
|
||||||
{
|
|
||||||
int poolId = 1;
|
|
||||||
Int64 requestId = 1000;
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
var trace = hc.GetTrace(nameof(DispatcherRenewJobOnRunServiceStopOnJobNotFoundExceptions));
|
|
||||||
TaskCompletionSource<int> firstJobRequestRenewed = new();
|
|
||||||
CancellationTokenSource cancellationTokenSource = new();
|
|
||||||
|
|
||||||
TaskAgentJobRequest request = new();
|
|
||||||
PropertyInfo lockUntilProperty = request.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
|
||||||
Assert.NotNull(lockUntilProperty);
|
|
||||||
lockUntilProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
|
|
||||||
|
|
||||||
hc.SetSingleton<IRunServer>(_runServer.Object);
|
|
||||||
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
|
||||||
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
|
|
||||||
_ = _runServer.Setup(x => x.RenewJobAsync(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
|
|
||||||
.Returns(() =>
|
|
||||||
{
|
|
||||||
count++;
|
|
||||||
if (!firstJobRequestRenewed.Task.IsCompletedSuccessfully)
|
|
||||||
{
|
|
||||||
trace.Info("First renew happens.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count < 5)
|
|
||||||
{
|
|
||||||
var response = new RenewJobResponse()
|
|
||||||
{
|
|
||||||
LockedUntil = request.LockedUntil.Value
|
|
||||||
};
|
|
||||||
return Task.FromResult<RenewJobResponse>(response);
|
|
||||||
}
|
|
||||||
else if (count == 5)
|
|
||||||
{
|
|
||||||
cancellationTokenSource.CancelAfter(10000);
|
|
||||||
throw new TaskOrchestrationJobNotFoundException("");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Should not reach here.");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
var jobDispatcher = new JobDispatcher();
|
|
||||||
jobDispatcher.Initialize(hc);
|
|
||||||
EnableRunServiceJobForJobDispatcher(jobDispatcher);
|
|
||||||
|
|
||||||
// Set the value of the _isRunServiceJob field to true
|
|
||||||
var isRunServiceJobField = typeof(JobDispatcher).GetField("_isRunServiceJob", BindingFlags.NonPublic | BindingFlags.Instance);
|
|
||||||
isRunServiceJobField.SetValue(jobDispatcher, true);
|
|
||||||
|
|
||||||
await jobDispatcher.RenewJobRequestAsync(GetAgentJobRequestMessage(), GetServiceEndpoint(), poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
|
||||||
|
|
||||||
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
|
|
||||||
Assert.False(cancellationTokenSource.IsCancellationRequested);
|
|
||||||
_runServer.Verify(x => x.RenewJobAsync(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()), Times.Exactly(5));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
@@ -333,7 +256,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
var jobDispatcher = new JobDispatcher();
|
var jobDispatcher = new JobDispatcher();
|
||||||
jobDispatcher.Initialize(hc);
|
jobDispatcher.Initialize(hc);
|
||||||
|
|
||||||
await jobDispatcher.RenewJobRequestAsync(It.IsAny<AgentJobRequestMessage>(), It.IsAny<ServiceEndpoint>(), poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
||||||
|
|
||||||
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
|
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
|
||||||
Assert.False(cancellationTokenSource.IsCancellationRequested);
|
Assert.False(cancellationTokenSource.IsCancellationRequested);
|
||||||
@@ -389,9 +312,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
var jobDispatcher = new JobDispatcher();
|
var jobDispatcher = new JobDispatcher();
|
||||||
jobDispatcher.Initialize(hc);
|
jobDispatcher.Initialize(hc);
|
||||||
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await jobDispatcher.RenewJobRequestAsync(It.IsAny<AgentJobRequestMessage>(), It.IsAny<ServiceEndpoint>(), 0, 0, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
await jobDispatcher.RenewJobRequestAsync(0, 0, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
_configurationStore.Verify(x => x.SaveSettings(It.Is<RunnerSettings>(settings => settings.AgentName == newName)), Times.Once);
|
_configurationStore.Verify(x => x.SaveSettings(It.Is<RunnerSettings>(settings => settings.AgentName == newName)), Times.Once);
|
||||||
@@ -446,7 +368,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
jobDispatcher.Initialize(hc);
|
jobDispatcher.Initialize(hc);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await jobDispatcher.RenewJobRequestAsync(It.IsAny<AgentJobRequestMessage>(), It.IsAny<ServiceEndpoint>(), 0, 0, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
await jobDispatcher.RenewJobRequestAsync(0, 0, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
_configurationStore.Verify(x => x.SaveSettings(It.IsAny<RunnerSettings>()), Times.Never);
|
_configurationStore.Verify(x => x.SaveSettings(It.IsAny<RunnerSettings>()), Times.Never);
|
||||||
@@ -499,7 +421,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
jobDispatcher.Initialize(hc);
|
jobDispatcher.Initialize(hc);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await jobDispatcher.RenewJobRequestAsync(It.IsAny<AgentJobRequestMessage>(), It.IsAny<ServiceEndpoint>(), 0, 0, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
await jobDispatcher.RenewJobRequestAsync(0, 0, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
_configurationStore.Verify(x => x.SaveSettings(It.IsAny<RunnerSettings>()), Times.Never);
|
_configurationStore.Verify(x => x.SaveSettings(It.IsAny<RunnerSettings>()), Times.Never);
|
||||||
@@ -557,7 +479,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
var jobDispatcher = new JobDispatcher();
|
var jobDispatcher = new JobDispatcher();
|
||||||
jobDispatcher.Initialize(hc);
|
jobDispatcher.Initialize(hc);
|
||||||
|
|
||||||
await jobDispatcher.RenewJobRequestAsync(It.IsAny<AgentJobRequestMessage>(), It.IsAny<ServiceEndpoint>(), poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
||||||
|
|
||||||
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
|
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
|
||||||
Assert.True(cancellationTokenSource.IsCancellationRequested);
|
Assert.True(cancellationTokenSource.IsCancellationRequested);
|
||||||
@@ -614,7 +536,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
var jobDispatcher = new JobDispatcher();
|
var jobDispatcher = new JobDispatcher();
|
||||||
jobDispatcher.Initialize(hc);
|
jobDispatcher.Initialize(hc);
|
||||||
|
|
||||||
await jobDispatcher.RenewJobRequestAsync(It.IsAny<AgentJobRequestMessage>(), It.IsAny<ServiceEndpoint>(), poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
||||||
|
|
||||||
Assert.False(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should failed.");
|
Assert.False(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should failed.");
|
||||||
Assert.False(cancellationTokenSource.IsCancellationRequested);
|
Assert.False(cancellationTokenSource.IsCancellationRequested);
|
||||||
@@ -678,7 +600,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
var jobDispatcher = new JobDispatcher();
|
var jobDispatcher = new JobDispatcher();
|
||||||
jobDispatcher.Initialize(hc);
|
jobDispatcher.Initialize(hc);
|
||||||
|
|
||||||
await jobDispatcher.RenewJobRequestAsync(It.IsAny<AgentJobRequestMessage>(), It.IsAny<ServiceEndpoint>(), poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
||||||
|
|
||||||
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
|
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
|
||||||
Assert.False(cancellationTokenSource.IsCancellationRequested);
|
Assert.False(cancellationTokenSource.IsCancellationRequested);
|
||||||
@@ -737,78 +659,5 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
Assert.True(jobDispatcher.RunOnceJobCompleted.Task.Result, "JobDispatcher should set task complete token to 'TRUE' for one time agent.");
|
Assert.True(jobDispatcher.RunOnceJobCompleted.Task.Result, "JobDispatcher should set task complete token to 'TRUE' for one time agent.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EnableRunServiceJobForJobDispatcher(JobDispatcher jobDispatcher)
|
|
||||||
{
|
|
||||||
// Set the value of the _isRunServiceJob field to true
|
|
||||||
var isRunServiceJobField = typeof(JobDispatcher).GetField("_isRunServiceJob", BindingFlags.NonPublic | BindingFlags.Instance);
|
|
||||||
isRunServiceJobField.SetValue(jobDispatcher, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ServiceEndpoint GetServiceEndpoint()
|
|
||||||
{
|
|
||||||
var serviceEndpoint = new ServiceEndpoint
|
|
||||||
{
|
|
||||||
Authorization = new EndpointAuthorization
|
|
||||||
{
|
|
||||||
Scheme = EndpointAuthorizationSchemes.OAuth
|
|
||||||
}
|
|
||||||
};
|
|
||||||
serviceEndpoint.Authorization.Parameters.Add("AccessToken", "token");
|
|
||||||
return serviceEndpoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AgentJobRequestMessage GetAgentJobRequestMessage()
|
|
||||||
{
|
|
||||||
var message = new AgentJobRequestMessage(
|
|
||||||
new TaskOrchestrationPlanReference()
|
|
||||||
{
|
|
||||||
PlanType = "Build",
|
|
||||||
PlanId = Guid.NewGuid(),
|
|
||||||
Version = 1
|
|
||||||
},
|
|
||||||
new TimelineReference()
|
|
||||||
{
|
|
||||||
Id = Guid.NewGuid()
|
|
||||||
},
|
|
||||||
Guid.NewGuid(),
|
|
||||||
"jobDisplayName",
|
|
||||||
"jobName",
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
new List<TemplateToken>(),
|
|
||||||
new Dictionary<string, VariableValue>()
|
|
||||||
{
|
|
||||||
{
|
|
||||||
"variables",
|
|
||||||
new VariableValue()
|
|
||||||
{
|
|
||||||
IsSecret = false,
|
|
||||||
Value = "variables"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new List<MaskHint>()
|
|
||||||
{
|
|
||||||
new MaskHint()
|
|
||||||
{
|
|
||||||
Type = MaskType.Variable,
|
|
||||||
Value = "maskHints"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new JobResources(),
|
|
||||||
new DictionaryContextData(),
|
|
||||||
new WorkspaceOptions(),
|
|
||||||
new List<JobStep>(),
|
|
||||||
new List<string>()
|
|
||||||
{
|
|
||||||
"fileTable"
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
new List<TemplateToken>(),
|
|
||||||
new ActionsEnvironmentReference("env")
|
|
||||||
);
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -502,34 +502,5 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.Once());
|
_messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.Once());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Runner")]
|
|
||||||
public async void TestRemoveLocalRunnerConfig()
|
|
||||||
{
|
|
||||||
using (var hc = new TestHostContext(this))
|
|
||||||
{
|
|
||||||
hc.SetSingleton<IConfigurationManager>(_configurationManager.Object);
|
|
||||||
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
|
||||||
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
|
||||||
|
|
||||||
var command = new CommandSettings(hc, new[] { "remove", "--local" });
|
|
||||||
|
|
||||||
_configStore.Setup(x => x.IsConfigured())
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
_configStore.Setup(x => x.HasCredentials())
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
|
|
||||||
var runner = new Runner.Listener.Runner();
|
|
||||||
runner.Initialize(hc);
|
|
||||||
await runner.ExecuteCommand(command);
|
|
||||||
|
|
||||||
// verify that we delete the local runner config with the correct remove parameter
|
|
||||||
_configurationManager.Verify(x => x.DeleteLocalRunnerConfig(), Times.Once());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,76 +128,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if OS_WINDOWS
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Common")]
|
|
||||||
public async Task SetTestEnvWithNullInKey()
|
|
||||||
{
|
|
||||||
using (TestHostContext hc = new(this))
|
|
||||||
{
|
|
||||||
Tracing trace = hc.GetTrace();
|
|
||||||
|
|
||||||
Int32 exitCode = -1;
|
|
||||||
var processInvoker = new ProcessInvokerWrapper();
|
|
||||||
processInvoker.Initialize(hc);
|
|
||||||
var stdout = new List<string>();
|
|
||||||
var stderr = new List<string>();
|
|
||||||
processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs e) =>
|
|
||||||
{
|
|
||||||
trace.Info(e.Data);
|
|
||||||
stdout.Add(e.Data);
|
|
||||||
};
|
|
||||||
processInvoker.ErrorDataReceived += (object sender, ProcessDataReceivedEventArgs e) =>
|
|
||||||
{
|
|
||||||
trace.Info(e.Data);
|
|
||||||
stderr.Add(e.Data);
|
|
||||||
};
|
|
||||||
|
|
||||||
exitCode = await processInvoker.ExecuteAsync("", "cmd.exe", "/c \"echo %TEST%\"", new Dictionary<string, string>() { { "TEST\0second", "first" } }, CancellationToken.None);
|
|
||||||
|
|
||||||
|
|
||||||
trace.Info("Exit Code: {0}", exitCode);
|
|
||||||
Assert.Equal(0, exitCode);
|
|
||||||
Assert.Equal("first", stdout.First(x => !string.IsNullOrWhiteSpace(x)));
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Common")]
|
|
||||||
public async Task SetTestEnvWithNullInValue()
|
|
||||||
{
|
|
||||||
using (TestHostContext hc = new(this))
|
|
||||||
{
|
|
||||||
Tracing trace = hc.GetTrace();
|
|
||||||
|
|
||||||
Int32 exitCode = -1;
|
|
||||||
var processInvoker = new ProcessInvokerWrapper();
|
|
||||||
processInvoker.Initialize(hc);
|
|
||||||
var stdout = new List<string>();
|
|
||||||
var stderr = new List<string>();
|
|
||||||
processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs e) =>
|
|
||||||
{
|
|
||||||
trace.Info(e.Data);
|
|
||||||
stdout.Add(e.Data);
|
|
||||||
};
|
|
||||||
processInvoker.ErrorDataReceived += (object sender, ProcessDataReceivedEventArgs e) =>
|
|
||||||
{
|
|
||||||
trace.Info(e.Data);
|
|
||||||
stderr.Add(e.Data);
|
|
||||||
};
|
|
||||||
|
|
||||||
exitCode = await processInvoker.ExecuteAsync("", "cmd.exe", "/c \"echo %TEST%\"", new Dictionary<string, string>() { { "TEST", "first\0second" } }, CancellationToken.None);
|
|
||||||
|
|
||||||
trace.Info("Exit Code: {0}", exitCode);
|
|
||||||
Assert.Equal(0, exitCode);
|
|
||||||
Assert.Equal("first", stdout.First(x => !string.IsNullOrWhiteSpace(x)));
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Common")]
|
[Trait("Category", "Common")]
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
Assert.False(proxy.IsBypassed(new Uri("https://actions.com")));
|
Assert.False(proxy.IsBypassed(new Uri("https://actions.com")));
|
||||||
Assert.False(proxy.IsBypassed(new Uri("https://ggithub.com")));
|
Assert.False(proxy.IsBypassed(new Uri("https://ggithub.com")));
|
||||||
Assert.False(proxy.IsBypassed(new Uri("https://github.comm")));
|
Assert.False(proxy.IsBypassed(new Uri("https://github.comm")));
|
||||||
Assert.False(proxy.IsBypassed(new Uri("https://google.com"))); // no_proxy has '.google.com', specifying only subdomains bypass
|
Assert.False(proxy.IsBypassed(new Uri("https://google.com")));
|
||||||
Assert.False(proxy.IsBypassed(new Uri("https://example.com")));
|
Assert.False(proxy.IsBypassed(new Uri("https://example.com")));
|
||||||
Assert.False(proxy.IsBypassed(new Uri("http://example.com:333")));
|
Assert.False(proxy.IsBypassed(new Uri("http://example.com:333")));
|
||||||
Assert.False(proxy.IsBypassed(new Uri("http://192.168.0.123:123")));
|
Assert.False(proxy.IsBypassed(new Uri("http://192.168.0.123:123")));
|
||||||
@@ -374,76 +374,6 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
CleanProxyEnv();
|
CleanProxyEnv();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Common")]
|
|
||||||
public void BypassAllOnWildcardNoProxy()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Environment.SetEnvironmentVariable("http_proxy", "http://user1:pass1%40@127.0.0.1:8888");
|
|
||||||
Environment.SetEnvironmentVariable("https_proxy", "http://user2:pass2%40@127.0.0.1:9999");
|
|
||||||
Environment.SetEnvironmentVariable("no_proxy", "example.com, * , example2.com");
|
|
||||||
var proxy = new RunnerWebProxy();
|
|
||||||
|
|
||||||
Assert.True(proxy.IsBypassed(new Uri("http://actions.com")));
|
|
||||||
Assert.True(proxy.IsBypassed(new Uri("http://localhost")));
|
|
||||||
Assert.True(proxy.IsBypassed(new Uri("http://127.0.0.1:8080")));
|
|
||||||
Assert.True(proxy.IsBypassed(new Uri("https://actions.com")));
|
|
||||||
Assert.True(proxy.IsBypassed(new Uri("https://localhost")));
|
|
||||||
Assert.True(proxy.IsBypassed(new Uri("https://127.0.0.1:8080")));
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
CleanProxyEnv();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Common")]
|
|
||||||
public void IgnoreWildcardInNoProxySubdomain()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Environment.SetEnvironmentVariable("http_proxy", "http://user1:pass1%40@127.0.0.1:8888");
|
|
||||||
Environment.SetEnvironmentVariable("https_proxy", "http://user2:pass2%40@127.0.0.1:9999");
|
|
||||||
Environment.SetEnvironmentVariable("no_proxy", "*.example.com");
|
|
||||||
var proxy = new RunnerWebProxy();
|
|
||||||
|
|
||||||
Assert.False(proxy.IsBypassed(new Uri("http://sub.example.com")));
|
|
||||||
Assert.False(proxy.IsBypassed(new Uri("http://example.com")));
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
CleanProxyEnv();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Common")]
|
|
||||||
public void WildcardNoProxyWorksWhenOtherNoProxyAreAround()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Environment.SetEnvironmentVariable("http_proxy", "http://user1:pass1%40@127.0.0.1:8888");
|
|
||||||
Environment.SetEnvironmentVariable("https_proxy", "http://user2:pass2%40@127.0.0.1:9999");
|
|
||||||
Environment.SetEnvironmentVariable("no_proxy", "example.com,*,example2.com");
|
|
||||||
var proxy = new RunnerWebProxy();
|
|
||||||
|
|
||||||
Assert.True(proxy.IsBypassed(new Uri("http://actions.com")));
|
|
||||||
Assert.True(proxy.IsBypassed(new Uri("http://localhost")));
|
|
||||||
Assert.True(proxy.IsBypassed(new Uri("http://127.0.0.1:8080")));
|
|
||||||
Assert.True(proxy.IsBypassed(new Uri("https://actions.com")));
|
|
||||||
Assert.True(proxy.IsBypassed(new Uri("https://localhost")));
|
|
||||||
Assert.True(proxy.IsBypassed(new Uri("https://127.0.0.1:8080")));
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
CleanProxyEnv();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Common.Util;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
@@ -185,19 +186,5 @@ namespace GitHub.Runner.Common.Tests.Util
|
|||||||
Assert.False(result9, $"'{undefineString3}' should convert to false.");
|
Assert.False(result9, $"'{undefineString3}' should convert to false.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("", "")]
|
|
||||||
[InlineData("(())", "[[]]")]
|
|
||||||
[InlineData("()()", "[][]")]
|
|
||||||
[InlineData(" Liquorix kernel OS Description is poorly formatted (linux version ", "Liquorix kernel OS Description is poorly formatted [linux version")]
|
|
||||||
[InlineData("Liquorix kernel OS Description is poorly formatted (linux version", "Liquorix kernel OS Description is poorly formatted [linux version")]
|
|
||||||
[InlineData("()((.", "[][[.")]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Common")]
|
|
||||||
public void SanitizeUserAgentHeader(string input, string expected)
|
|
||||||
{
|
|
||||||
Assert.Equal(expected, StringUtil.SanitizeUserAgentHeader(input));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,20 @@ using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
|||||||
using GitHub.DistributedTask.Pipelines;
|
using GitHub.DistributedTask.Pipelines;
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Runner.Common.Util;
|
||||||
using GitHub.Runner.Worker;
|
using GitHub.Runner.Worker;
|
||||||
|
using GitHub.Runner.Worker.Container;
|
||||||
using GitHub.Runner.Worker.Handlers;
|
using GitHub.Runner.Worker.Handlers;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||||
|
|
||||||
@@ -143,12 +149,11 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_context.Add("matrix", matrixData);
|
_context.Add("matrix", matrixData);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
// Should report success with no updated required if there's already a valid display name.
|
// Should not do anything if we don't have a displayNameToken to expand
|
||||||
var validDisplayName = _actionRunner.EvaluateDisplayName(_context, _actionRunner.ExecutionContext, out bool updated);
|
var didUpdateDisplayName = _actionRunner.TryEvaluateDisplayName(_context, _actionRunner.ExecutionContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(validDisplayName);
|
Assert.False(didUpdateDisplayName);
|
||||||
Assert.False(updated);
|
|
||||||
Assert.Equal(actionDisplayName, _actionRunner.DisplayName);
|
Assert.Equal(actionDisplayName, _actionRunner.DisplayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,51 +183,13 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
// Act
|
// Act
|
||||||
// Should expand the displaynameToken and set the display name to that
|
// Should expand the displaynameToken and set the display name to that
|
||||||
var validDisplayName = _actionRunner.EvaluateDisplayName(_context, _actionRunner.ExecutionContext, out bool updated);
|
var didUpdateDisplayName = _actionRunner.TryEvaluateDisplayName(_context, _actionRunner.ExecutionContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(validDisplayName);
|
Assert.True(didUpdateDisplayName);
|
||||||
Assert.True(updated);
|
|
||||||
Assert.Equal(expectedString, _actionRunner.DisplayName);
|
Assert.Equal(expectedString, _actionRunner.DisplayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Worker")]
|
|
||||||
public void IgnoreDisplayNameTokenWhenDisplayNameIsExplicitlySet()
|
|
||||||
{
|
|
||||||
var explicitDisplayName = "Explcitly Set Name";
|
|
||||||
|
|
||||||
// Arrange
|
|
||||||
Setup();
|
|
||||||
var actionId = Guid.NewGuid();
|
|
||||||
var action = new Pipelines.ActionStep()
|
|
||||||
{
|
|
||||||
Name = "action",
|
|
||||||
Id = actionId,
|
|
||||||
DisplayName = explicitDisplayName,
|
|
||||||
DisplayNameToken = new BasicExpressionToken(null, null, null, "matrix.node"),
|
|
||||||
};
|
|
||||||
|
|
||||||
_actionRunner.Action = action;
|
|
||||||
|
|
||||||
var matrixData = new DictionaryContextData
|
|
||||||
{
|
|
||||||
["node"] = new StringContextData("8")
|
|
||||||
};
|
|
||||||
_context.Add("matrix", matrixData);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
// Should ignore the displayNameToken since there's already an explicit value for DisplayName
|
|
||||||
var validDisplayName = _actionRunner.EvaluateDisplayName(_context, _actionRunner.ExecutionContext, out bool updated);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.True(validDisplayName);
|
|
||||||
Assert.False(updated);
|
|
||||||
Assert.Equal(explicitDisplayName, _actionRunner.DisplayName);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
@@ -251,11 +218,10 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
// Act
|
// Act
|
||||||
// Should expand the displaynameToken and set the display name to that
|
// Should expand the displaynameToken and set the display name to that
|
||||||
var validDisplayName = _actionRunner.EvaluateDisplayName(_context, _actionRunner.ExecutionContext, out bool updated);
|
var didUpdateDisplayName = _actionRunner.TryEvaluateDisplayName(_context, _actionRunner.ExecutionContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(validDisplayName);
|
Assert.True(didUpdateDisplayName);
|
||||||
Assert.True(updated);
|
|
||||||
Assert.Equal("Run 8", _actionRunner.DisplayName);
|
Assert.Equal("Run 8", _actionRunner.DisplayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,11 +246,10 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
// Act
|
// Act
|
||||||
// Should expand the displaynameToken and set the display name to that
|
// Should expand the displaynameToken and set the display name to that
|
||||||
var validDisplayName = _actionRunner.EvaluateDisplayName(_context, _actionRunner.ExecutionContext, out bool updated);
|
var didUpdateDisplayName = _actionRunner.TryEvaluateDisplayName(_context, _actionRunner.ExecutionContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(validDisplayName);
|
Assert.True(didUpdateDisplayName);
|
||||||
Assert.True(updated);
|
|
||||||
Assert.Equal("Run TestImageName:latest", _actionRunner.DisplayName);
|
Assert.Equal("Run TestImageName:latest", _actionRunner.DisplayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,11 +272,10 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
// Act
|
// Act
|
||||||
// Should not do anything if we don't have context on the display name
|
// Should not do anything if we don't have context on the display name
|
||||||
var validDisplayName = _actionRunner.EvaluateDisplayName(_context, _actionRunner.ExecutionContext, out bool updated);
|
var didUpdateDisplayName = _actionRunner.TryEvaluateDisplayName(_context, _actionRunner.ExecutionContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(validDisplayName);
|
Assert.False(didUpdateDisplayName);
|
||||||
Assert.False(updated);
|
|
||||||
// Should use the pretty display name until we can eval
|
// Should use the pretty display name until we can eval
|
||||||
Assert.Equal("${{ matrix.node }}", _actionRunner.DisplayName);
|
Assert.Equal("${{ matrix.node }}", _actionRunner.DisplayName);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,46 +99,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Worker")]
|
|
||||||
public void InitializeWithCorrectManager()
|
|
||||||
{
|
|
||||||
containers.Add(new ContainerInfo() { ContainerImage = "ubuntu:16.04" });
|
|
||||||
_hc = new TestHostContext(this, "Test");
|
|
||||||
_ec = new Mock<IExecutionContext>();
|
|
||||||
serverQueue = new Mock<IJobServerQueue>();
|
|
||||||
pagingLogger = new Mock<IPagingLogger>();
|
|
||||||
|
|
||||||
containerOperationProvider = new ContainerOperationProvider();
|
|
||||||
|
|
||||||
_hc.SetSingleton<IJobServerQueue>(serverQueue.Object);
|
|
||||||
_hc.SetSingleton<IPagingLogger>(pagingLogger.Object);
|
|
||||||
|
|
||||||
|
|
||||||
_ec.Setup(x => x.Global).Returns(new GlobalContext());
|
|
||||||
|
|
||||||
Environment.SetEnvironmentVariable(Constants.Hooks.ContainerHooksPath, "/tmp/k8s/index.js");
|
|
||||||
_dockerManager = new Mock<IDockerCommandManager>();
|
|
||||||
_dockerManager.Setup(x => x.Initialize(_hc)).Throws(new Exception("Docker manager's Initialize should not be called"));
|
|
||||||
|
|
||||||
_containerHookManager = new Mock<IContainerHookManager>();
|
|
||||||
_hc.SetSingleton<IDockerCommandManager>(_dockerManager.Object);
|
|
||||||
_hc.SetSingleton<IContainerHookManager>(_containerHookManager.Object);
|
|
||||||
|
|
||||||
containerOperationProvider.Initialize(_hc);
|
|
||||||
|
|
||||||
Environment.SetEnvironmentVariable(Constants.Hooks.ContainerHooksPath, null);
|
|
||||||
_containerHookManager = new Mock<IContainerHookManager>();
|
|
||||||
_containerHookManager.Setup(x => x.Initialize(_hc)).Throws(new Exception("Container hook manager's Initialize should not be called"));
|
|
||||||
|
|
||||||
_dockerManager = new Mock<IDockerCommandManager>();
|
|
||||||
_hc.SetSingleton<IDockerCommandManager>(_dockerManager.Object);
|
|
||||||
_hc.SetSingleton<IContainerHookManager>(_containerHookManager.Object);
|
|
||||||
|
|
||||||
containerOperationProvider.Initialize(_hc);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Setup([CallerMemberName] string testName = "")
|
private void Setup([CallerMemberName] string testName = "")
|
||||||
{
|
{
|
||||||
containers.Add(new ContainerInfo() { ContainerImage = "ubuntu:16.04" });
|
containers.Add(new ContainerInfo() { ContainerImage = "ubuntu:16.04" });
|
||||||
@@ -151,6 +111,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_containerHookManager = new Mock<IContainerHookManager>();
|
_containerHookManager = new Mock<IContainerHookManager>();
|
||||||
containerOperationProvider = new ContainerOperationProvider();
|
containerOperationProvider = new ContainerOperationProvider();
|
||||||
|
|
||||||
|
_hc.SetSingleton<IDockerCommandManager>(_dockerManager.Object);
|
||||||
_hc.SetSingleton<IJobServerQueue>(serverQueue.Object);
|
_hc.SetSingleton<IJobServerQueue>(serverQueue.Object);
|
||||||
_hc.SetSingleton<IPagingLogger>(pagingLogger.Object);
|
_hc.SetSingleton<IPagingLogger>(pagingLogger.Object);
|
||||||
|
|
||||||
|
|||||||
@@ -711,63 +711,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Worker")]
|
|
||||||
public void PublishStepResult_EmbeddedStep()
|
|
||||||
{
|
|
||||||
using (TestHostContext hc = CreateTestContext())
|
|
||||||
{
|
|
||||||
// Arrange: Create a job request message.
|
|
||||||
TaskOrchestrationPlanReference plan = new();
|
|
||||||
TimelineReference timeline = new();
|
|
||||||
Guid jobId = Guid.NewGuid();
|
|
||||||
string jobName = "some job name";
|
|
||||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
|
|
||||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
|
||||||
{
|
|
||||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
|
||||||
Id = "github",
|
|
||||||
Version = "sha1"
|
|
||||||
});
|
|
||||||
jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
|
|
||||||
|
|
||||||
// Arrange: Setup the paging logger.
|
|
||||||
var pagingLogger = new Mock<IPagingLogger>();
|
|
||||||
var pagingLogger2 = new Mock<IPagingLogger>();
|
|
||||||
var jobServerQueue = new Mock<IJobServerQueue>();
|
|
||||||
jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.IsAny<TimelineRecord>()));
|
|
||||||
|
|
||||||
hc.EnqueueInstance(pagingLogger.Object);
|
|
||||||
hc.EnqueueInstance(pagingLogger2.Object);
|
|
||||||
hc.SetSingleton(jobServerQueue.Object);
|
|
||||||
|
|
||||||
var ec = new Runner.Worker.ExecutionContext();
|
|
||||||
ec.Initialize(hc);
|
|
||||||
|
|
||||||
// Act.
|
|
||||||
ec.InitializeJob(jobRequest, CancellationToken.None);
|
|
||||||
ec.Start();
|
|
||||||
|
|
||||||
var embeddedStep = ec.CreateChild(Guid.NewGuid(), "action_1_pre", "action_1_pre", null, null, ActionRunStage.Main, isEmbedded: true);
|
|
||||||
embeddedStep.Start();
|
|
||||||
|
|
||||||
embeddedStep.StepTelemetry.Type = "node16";
|
|
||||||
embeddedStep.StepTelemetry.Action = "actions/checkout";
|
|
||||||
embeddedStep.StepTelemetry.Ref = "v2";
|
|
||||||
|
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" });
|
|
||||||
|
|
||||||
ec.Complete();
|
|
||||||
|
|
||||||
// Assert.
|
|
||||||
Assert.Equal(1, ec.Global.StepsResult.Count);
|
|
||||||
Assert.Equal(TaskResult.Succeeded, ec.Global.StepsResult.Single().Conclusion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
|
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
|
||||||
{
|
{
|
||||||
var hc = new TestHostContext(this, testName);
|
var hc = new TestHostContext(this, testName);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user