mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:36:23 +00:00
Compare commits
89 Commits
releases/m
...
v2.324.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da3412efd4 | ||
|
|
26185d43d0 | ||
|
|
e911d2908d | ||
|
|
ce4b7f4dd6 | ||
|
|
505fa60905 | ||
|
|
57459ad274 | ||
|
|
890e43f6c5 | ||
|
|
3a27ca292a | ||
|
|
282f7cd2b2 | ||
|
|
f060fe5c85 | ||
|
|
1a092a24a3 | ||
|
|
26eff8e55a | ||
|
|
d7cfd2e341 | ||
|
|
a3a7b6a77e | ||
|
|
db6005b0a7 | ||
|
|
9155c42c09 | ||
|
|
1c319b4d42 | ||
|
|
fe10d4ae82 | ||
|
|
27d9c886ab | ||
|
|
5106d6578e | ||
|
|
d5ccbd10d1 | ||
|
|
f1b5b5bd5c | ||
|
|
aaf1b92847 | ||
|
|
c1095ae2d1 | ||
|
|
a0a0a76378 | ||
|
|
d47013928b | ||
|
|
cdeec012aa | ||
|
|
2cb1f9431a | ||
|
|
e86c9487ab | ||
|
|
dc9695f123 | ||
|
|
6654f6b3de | ||
|
|
f5e4e7e47c | ||
|
|
68ca457917 | ||
|
|
77700abf81 | ||
|
|
a0ba8fd399 | ||
|
|
6b08f23b6c | ||
|
|
8131246933 | ||
|
|
7211a53c9e | ||
|
|
07310cabc0 | ||
|
|
0195d7ca77 | ||
|
|
259af3eda2 | ||
|
|
0ce29d09c6 | ||
|
|
a84e1c2b15 | ||
|
|
de51cd0ed6 | ||
|
|
3333de3a36 | ||
|
|
b065e5abbe | ||
|
|
bae52e28f9 | ||
|
|
c2c91438e8 | ||
|
|
3486c54ccb | ||
|
|
a61328a7e7 | ||
|
|
52dc98b10f | ||
|
|
a7b319530e | ||
|
|
54f082722f | ||
|
|
ed9d8fc9f7 | ||
|
|
fccbe8fb0b | ||
|
|
e3bc10a931 | ||
|
|
ba50bf6482 | ||
|
|
8eef71d93d | ||
|
|
7ae9fc03a2 | ||
|
|
8e97ad4d86 | ||
|
|
aa76aa476f | ||
|
|
0738df9702 | ||
|
|
8bf52ffe7d | ||
|
|
9df3fc825d | ||
|
|
fde5227fbf | ||
|
|
27f6ca8177 | ||
|
|
078eb3b381 | ||
|
|
c46dac6736 | ||
|
|
e640a9fef3 | ||
|
|
6d266a7c44 | ||
|
|
4700649bb5 | ||
|
|
27580ef8de | ||
|
|
6c94f78f37 | ||
|
|
074d9c0922 | ||
|
|
59f2be2158 | ||
|
|
1e1f7845fa | ||
|
|
694ae12b23 | ||
|
|
d16fb2c593 | ||
|
|
d37a7ae14d | ||
|
|
6ef5803f24 | ||
|
|
2c03d74f11 | ||
|
|
3d34a3c6d6 | ||
|
|
59ec9b4139 | ||
|
|
4a99838fa2 | ||
|
|
af8dee51e1 | ||
|
|
9b3b554758 | ||
|
|
4d8402c260 | ||
|
|
aa0ee2bf64 | ||
|
|
dcc64fead2 |
@@ -4,10 +4,10 @@
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
||||
"ghcr.io/devcontainers/features/dotnet": {
|
||||
"version": "6.0.421"
|
||||
"version": "8.0.408"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "16"
|
||||
"version": "20"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/sshd:1": {
|
||||
"version": "latest"
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -7,7 +7,7 @@ contact_links:
|
||||
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.
|
||||
- name: ✅ Feedback and suggestions for GitHub Actions
|
||||
url: https://github.com/github/feedback/discussions/categories/actions-and-packages-feedback
|
||||
url: https://github.com/github/feedback/discussions/categories/actions
|
||||
about: If you have feedback or suggestions about GitHub Actions, please open a discussion (or add to an existing one) in the GitHub Actions Feedback. GitHub Actions Product Managers and Engineers monitor the feedback forum.
|
||||
- name: ‼️ GitHub Security Bug Bounty
|
||||
url: https://bounty.github.com/
|
||||
|
||||
25
.github/copilot-instructions.md
vendored
Normal file
25
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
## Making changes
|
||||
|
||||
### Tests
|
||||
|
||||
Whenever possible, changes should be accompanied by non-trivial tests that meaningfully exercise the core functionality of the new code being introduced.
|
||||
|
||||
All tests are in the `Test/` directory at the repo root. Fast unit tests are in the `Test/L0` directory and by convention have the suffix `L0.cs`. For example: unit tests for a hypothetical `src/Runner.Worker/Foo.cs` would go in `src/Test/L0/Worker/FooL0.cs`.
|
||||
|
||||
Run tests using this command:
|
||||
|
||||
```sh
|
||||
cd src && ./dev.sh test
|
||||
```
|
||||
|
||||
### Formatting
|
||||
|
||||
After editing .cs files, always format the code using this command:
|
||||
|
||||
```sh
|
||||
cd src && ./dev.sh format
|
||||
```
|
||||
|
||||
### Feature Flags
|
||||
|
||||
Wherever possible, all changes should be safeguarded by a feature flag; `Features` are declared in [Constants.cs](src/Runner.Common/Constants.cs).
|
||||
5
.github/dependabot.yml
vendored
5
.github/dependabot.yml
vendored
@@ -5,6 +5,11 @@ updates:
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: "main"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: "main"
|
||||
- package-ecosystem: "nuget"
|
||||
directory: "/src"
|
||||
schedule:
|
||||
|
||||
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Build runner layout
|
||||
- name: Build & Layout Release
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
- name: Package Release
|
||||
if: github.event_name != 'pull_request'
|
||||
run: |
|
||||
${{ matrix.devScript }} package Release
|
||||
${{ matrix.devScript }} package Release ${{ matrix.runtime }}
|
||||
working-directory: src
|
||||
|
||||
# Upload runner package tar.gz/zip as artifact
|
||||
|
||||
2
.github/workflows/close-bugs-bot.yml
vendored
2
.github/workflows/close-bugs-bot.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v8
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
close-issue-message: "This issue does not seem to be a problem with the runner application, it concerns the GitHub actions platform more generally. Could you please post your feedback on the [GitHub Community Support Forum](https://github.com/orgs/community/discussions/categories/actions) which is actively monitored. Using the forum ensures that we route your problem to the correct team. 😃"
|
||||
exempt-issue-labels: "keep"
|
||||
|
||||
2
.github/workflows/close-features-bot.yml
vendored
2
.github/workflows/close-features-bot.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v8
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
close-issue-message: "Thank you for your interest in the runner application and taking the time to provide your valuable feedback. We kindly ask you to redirect this feedback to the [GitHub Community Support Forum](https://github.com/orgs/community/discussions/categories/actions-and-packages) which our team actively monitors and would be a better place to start a discussion for new feature requests in GitHub Actions. For more information on this policy please [read our contribution guidelines](https://github.com/actions/runner#contribute). 😃"
|
||||
exempt-issue-labels: "keep"
|
||||
|
||||
6
.github/workflows/codeql.yml
vendored
6
.github/workflows/codeql.yml
vendored
@@ -23,11 +23,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v3
|
||||
# Override language selection by uncommenting this and choosing your languages
|
||||
# with:
|
||||
# languages: go, javascript, csharp, python, cpp, java
|
||||
@@ -38,4 +38,4 @@ jobs:
|
||||
working-directory: src
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
|
||||
6
.github/workflows/dotnet-upgrade.yml
vendored
6
.github/workflows/dotnet-upgrade.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
DOTNET_CURRENT_MAJOR_MINOR_VERSION: ${{ steps.fetch_current_version.outputs.DOTNET_CURRENT_MAJOR_MINOR_VERSION }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Get current major minor version
|
||||
id: fetch_current_version
|
||||
shell: bash
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
run: echo "::error links::feature/dotnet-sdk-upgrade${{ steps.fetch_latest_version.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }} https://github.com/actions/runner/tree/feature/dotnet-sdk-upgrade${{ steps.fetch_latest_version.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}::Branch feature/dotnetsdk-upgrade/${{ steps.fetch_latest_version.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }} already exists. Please take a look and delete that branch if you wish to recreate"
|
||||
- name: Create a warning annotation if no need to update
|
||||
if: ${{ steps.fetch_latest_version.outputs.SHOULD_UPDATE == 0 && steps.fetch_latest_version.outputs.BRANCH_EXISTS == 0 }}
|
||||
run: echo "::warning ::Latest DotNet SDK patch is ${{ steps.fetch_latest_version.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}, and we are on ${{ steps.fetch_latest_version.outputs.DOTNET_CURRENT_MAJOR_MINOR_PATCH_VERSION }}. No need to update"
|
||||
run: echo "::warning ::Latest DotNet SDK patch is ${{ steps.fetch_latest_version.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}, and we are on ${{ steps.fetch_current_version.outputs.DOTNET_CURRENT_MAJOR_MINOR_PATCH_VERSION }}. No need to update"
|
||||
- name: Update patch version
|
||||
if: ${{ steps.fetch_latest_version.outputs.SHOULD_UPDATE == 1 && steps.fetch_latest_version.outputs.BRANCH_EXISTS == 0 }}
|
||||
shell: bash
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
if: ${{ needs.dotnet-update.outputs.SHOULD_UPDATE == 1 && needs.dotnet-update.outputs.BRANCH_EXISTS == 0 }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: feature/dotnetsdk-upgrade/${{ needs.dotnet-update.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}
|
||||
- name: Create Pull Request
|
||||
|
||||
24
.github/workflows/lint.yml
vendored
24
.github/workflows/lint.yml
vendored
@@ -1,24 +0,0 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Ensure full list of changed files within `super-linter`
|
||||
fetch-depth: 0
|
||||
- name: Run linters
|
||||
uses: github/super-linter@v4
|
||||
env:
|
||||
DEFAULT_BRANCH: ${{ github.base_ref }}
|
||||
EDITORCONFIG_FILE_NAME: .editorconfig
|
||||
LINTER_RULES_PATH: /src/
|
||||
VALIDATE_ALL_CODEBASE: false
|
||||
VALIDATE_CSHARP: true
|
||||
70
.github/workflows/publish-image.yml
vendored
70
.github/workflows/publish-image.yml
vendored
@@ -1,70 +0,0 @@
|
||||
name: Publish Runner Image
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
runnerVersion:
|
||||
type: string
|
||||
description: Version of the runner being installed
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository_owner }}/actions-runner
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Compute image version
|
||||
id: image
|
||||
uses: actions/github-script@v6
|
||||
env:
|
||||
RUNNER_VERSION: ${{ github.event.inputs.runnerVersion }}
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const inputRunnerVersion = process.env.RUNNER_VERSION;
|
||||
if (inputRunnerVersion) {
|
||||
console.log(`Using input runner version ${inputRunnerVersion}`)
|
||||
core.setOutput('version', inputRunnerVersion);
|
||||
return
|
||||
}
|
||||
const runnerVersion = fs.readFileSync('${{ github.workspace }}/src/runnerversion', 'utf8').replace(/\n$/g, '')
|
||||
console.log(`Using runner version ${runnerVersion}`)
|
||||
core.setOutput('version', runnerVersion);
|
||||
|
||||
- name: Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Log into registry ${{ env.REGISTRY }}
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: build-and-push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: ./images
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.image.outputs.version }}
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
build-args: |
|
||||
RUNNER_VERSION=${{ steps.image.outputs.version }}
|
||||
push: true
|
||||
labels: |
|
||||
org.opencontainers.image.source=${{github.server_url}}/${{github.repository}}
|
||||
org.opencontainers.image.description=https://github.com/actions/runner/releases/tag/v${{ steps.image.outputs.version }}
|
||||
org.opencontainers.image.licenses=MIT
|
||||
84
.github/workflows/release.yml
vendored
84
.github/workflows/release.yml
vendored
@@ -11,16 +11,15 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/heads/releases/') || github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Make sure ./releaseVersion match ./src/runnerversion
|
||||
# Query GitHub release ensure version is not used
|
||||
- name: Check version
|
||||
uses: actions/github-script@0.3.0
|
||||
uses: actions/github-script@v7.0.1
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
const core = require('@actions/core')
|
||||
const fs = require('fs');
|
||||
const runnerVersion = fs.readFileSync('${{ github.workspace }}/src/runnerversion', 'utf8').replace(/\n$/g, '')
|
||||
const releaseVersion = fs.readFileSync('${{ github.workspace }}/releaseVersion', 'utf8').replace(/\n$/g, '')
|
||||
@@ -30,7 +29,7 @@ jobs:
|
||||
return
|
||||
}
|
||||
try {
|
||||
const release = await github.repos.getReleaseByTag({
|
||||
const release = await github.rest.repos.getReleaseByTag({
|
||||
owner: '${{ github.event.repository.owner.name }}',
|
||||
repo: '${{ github.event.repository.name }}',
|
||||
tag: 'v' + runnerVersion
|
||||
@@ -87,7 +86,7 @@ jobs:
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Build runner layout
|
||||
- name: Build & Layout Release
|
||||
@@ -117,12 +116,11 @@ jobs:
|
||||
working-directory: _package
|
||||
|
||||
# Upload runner package tar.gz/zip as artifact.
|
||||
# Since each package name is unique, so we don't need to put ${{matrix}} info into artifact name
|
||||
- name: Publish Artifact
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: runner-packages
|
||||
name: runner-packages-${{ matrix.runtime }}
|
||||
path: |
|
||||
_package
|
||||
|
||||
@@ -131,23 +129,52 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Download runner package tar.gz/zip produced by 'build' job
|
||||
- name: Download Artifact
|
||||
- name: Download Artifact (win-x64)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: runner-packages
|
||||
name: runner-packages-win-x64
|
||||
path: ./
|
||||
- name: Download Artifact (win-arm64)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: runner-packages-win-arm64
|
||||
path: ./
|
||||
- name: Download Artifact (osx-x64)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: runner-packages-osx-x64
|
||||
path: ./
|
||||
- name: Download Artifact (osx-arm64)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: runner-packages-osx-arm64
|
||||
path: ./
|
||||
- name: Download Artifact (linux-x64)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: runner-packages-linux-x64
|
||||
path: ./
|
||||
- name: Download Artifact (linux-arm)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: runner-packages-linux-arm
|
||||
path: ./
|
||||
- name: Download Artifact (linux-arm64)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: runner-packages-linux-arm64
|
||||
path: ./
|
||||
|
||||
# Create ReleaseNote file
|
||||
- name: Create ReleaseNote
|
||||
id: releaseNote
|
||||
uses: actions/github-script@0.3.0
|
||||
uses: actions/github-script@v7.0.1
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
const core = require('@actions/core')
|
||||
const fs = require('fs');
|
||||
const runnerVersion = fs.readFileSync('${{ github.workspace }}/src/runnerversion', 'utf8').replace(/\n$/g, '')
|
||||
var releaseNote = fs.readFileSync('${{ github.workspace }}/releaseNote.md', 'utf8').replace(/<RUNNER_VERSION>/g, runnerVersion)
|
||||
@@ -187,7 +214,7 @@ jobs:
|
||||
|
||||
# Upload release assets (full runner packages)
|
||||
- name: Upload Release Asset (win-x64)
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
uses: actions/upload-release-asset@v1.0.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
@@ -197,7 +224,7 @@ jobs:
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload Release Asset (win-arm64)
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
uses: actions/upload-release-asset@v1.0.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
@@ -207,7 +234,7 @@ jobs:
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload Release Asset (linux-x64)
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
uses: actions/upload-release-asset@v1.0.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
@@ -217,7 +244,7 @@ jobs:
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload Release Asset (osx-x64)
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
uses: actions/upload-release-asset@v1.0.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
@@ -227,7 +254,7 @@ jobs:
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload Release Asset (osx-arm64)
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
uses: actions/upload-release-asset@v1.0.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
@@ -237,7 +264,7 @@ jobs:
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload Release Asset (linux-arm)
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
uses: actions/upload-release-asset@v1.0.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
@@ -247,7 +274,7 @@ jobs:
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload Release Asset (linux-arm64)
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
uses: actions/upload-release-asset@v1.0.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
@@ -262,16 +289,18 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository_owner }}/actions-runner
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Compute image version
|
||||
id: image
|
||||
uses: actions/github-script@v6
|
||||
uses: actions/github-script@v7.0.1
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
@@ -280,10 +309,10 @@ jobs:
|
||||
core.setOutput('version', runnerVersion);
|
||||
|
||||
- name: Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log into registry ${{ env.REGISTRY }}
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -291,7 +320,7 @@ jobs:
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: build-and-push
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./images
|
||||
platforms: |
|
||||
@@ -307,3 +336,10 @@ jobs:
|
||||
org.opencontainers.image.source=${{github.server_url}}/${{github.repository}}
|
||||
org.opencontainers.image.description=https://github.com/actions/runner/releases/tag/v${{ steps.image.outputs.version }}
|
||||
org.opencontainers.image.licenses=MIT
|
||||
|
||||
- name: Generate attestation
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
subject-digest: ${{ steps.build-and-push.outputs.digest }}
|
||||
push-to-registry: true
|
||||
|
||||
2
.github/workflows/stale-bot.yml
vendored
2
.github/workflows/stale-bot.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v8
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
stale-issue-message: "This issue is stale because it has been open 365 days with no activity. Remove stale label or comment or this will be closed in 15 days."
|
||||
close-issue-message: "This issue was closed because it has been stalled for 15 days with no activity."
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -26,4 +26,5 @@ _dotnetsdk
|
||||
TestResults
|
||||
TestLogs
|
||||
.DS_Store
|
||||
.mono
|
||||
**/*.DotSettings.user
|
||||
@@ -250,6 +250,42 @@ Two problem matchers can be used:
|
||||
}
|
||||
```
|
||||
|
||||
#### Default from path
|
||||
|
||||
The problem matcher can specify a `fromPath` property at the top level, which applies when a specific pattern doesn't provide a value for `fromPath`. This is useful for tools that don't include project file information in their output.
|
||||
|
||||
For example, given the following compiler output that doesn't include project file information:
|
||||
|
||||
```
|
||||
ClassLibrary.cs(16,24): warning CS0612: 'ClassLibrary.Helpers.MyHelper.Name' is obsolete
|
||||
```
|
||||
|
||||
A problem matcher with a default from path can be used:
|
||||
|
||||
```json
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "csc-minimal",
|
||||
"fromPath": "ClassLibrary/ClassLibrary.csproj",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.+)\\((\\d+),(\\d+)\\): (error|warning) (.+): (.*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3,
|
||||
"severity": 4,
|
||||
"code": 5,
|
||||
"message": 6
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
This ensures that the file is rooted to the correct path when there's not enough information in the error messages to extract a `fromPath`.
|
||||
|
||||
#### Mitigate regular expression denial of service (ReDos)
|
||||
|
||||
If a matcher exceeds a 1 second timeout when processing a line, retry up to two three times total.
|
||||
|
||||
@@ -23,7 +23,7 @@ This feature is mainly intended for self hosted runner administrators.
|
||||
- `ACTIONS_RUNNER_HOOK_JOB_STARTED`
|
||||
- `ACTIONS_RUNNER_HOOK_JOB_COMPLETED`
|
||||
|
||||
You can set these variables to the **absolute** path of a a `.sh` or `.ps1` file.
|
||||
You can set these variables to the **absolute** path of a `.sh` or `.ps1` file.
|
||||
|
||||
We will execute `pwsh` (fallback to `powershell`) or `bash` (fallback to `sh`) as appropriate.
|
||||
- `.sh` files will execute with the args `-e {pathtofile}`
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
Make sure the built-in node.js has access to GitHub.com or GitHub Enterprise Server.
|
||||
|
||||
The runner carries its own copy of node.js executable under `<runner_root>/externals/node16/`.
|
||||
The runner carries its own copy of node.js executable under `<runner_root>/externals/node20/`.
|
||||
|
||||
All javascript base Actions will get executed by the built-in `node` at `<runner_root>/externals/node16/`.
|
||||
All javascript base Actions will get executed by the built-in `node` at `<runner_root>/externals/node20/`.
|
||||
|
||||
> Not the `node` from `$PATH`
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Source: https://github.com/dotnet/dotnet-docker
|
||||
FROM mcr.microsoft.com/dotnet/runtime-deps:6.0-jammy as build
|
||||
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy AS build
|
||||
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ARG RUNNER_VERSION
|
||||
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.6.1
|
||||
ARG DOCKER_VERSION=27.1.1
|
||||
ARG BUILDX_VERSION=0.16.2
|
||||
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.7.0
|
||||
ARG DOCKER_VERSION=28.1.1
|
||||
ARG BUILDX_VERSION=0.23.0
|
||||
|
||||
RUN apt update -y && apt install curl unzip -y
|
||||
|
||||
@@ -32,7 +32,7 @@ RUN export RUNNER_ARCH=${TARGETARCH} \
|
||||
"https://github.com/docker/buildx/releases/download/v${BUILDX_VERSION}/buildx-v${BUILDX_VERSION}.linux-${TARGETARCH}" \
|
||||
&& chmod +x /usr/local/lib/docker/cli-plugins/docker-buildx
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/runtime-deps:6.0-jammy
|
||||
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV RUNNER_MANUALLY_TRAP_SIG=1
|
||||
@@ -47,7 +47,8 @@ RUN apt update -y \
|
||||
# Configure git-core/ppa based on guidance here: https://git-scm.com/download/linux
|
||||
RUN add-apt-repository ppa:git-core/ppa \
|
||||
&& apt update -y \
|
||||
&& apt install -y --no-install-recommends git
|
||||
&& apt install -y git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN adduser --disabled-password --gecos "" --uid 1001 runner \
|
||||
&& groupadd docker --gid 123 \
|
||||
|
||||
@@ -1,17 +1,37 @@
|
||||
## What's Changed
|
||||
* Increase error body max length before truncation by @ericsciple in https://github.com/actions/runner/pull/3762
|
||||
* Fix release.yml break by upgrading actions/github-script by @TingluoHuang in https://github.com/actions/runner/pull/3772
|
||||
* Small runner code cleanup. by @TingluoHuang in https://github.com/actions/runner/pull/3773
|
||||
* Enable hostcontext to track auth migration. by @TingluoHuang in https://github.com/actions/runner/pull/3776
|
||||
* Add option in OAuthCred to load authUrlV2. by @TingluoHuang in https://github.com/actions/runner/pull/3777
|
||||
* Remove create session with broker in MessageListener. by @TingluoHuang in https://github.com/actions/runner/pull/3782
|
||||
* Enable auth migration based on config refresh. by @TingluoHuang in https://github.com/actions/runner/pull/3786
|
||||
* Set JWT.alg to PS256 with PssPadding. by @TingluoHuang in https://github.com/actions/runner/pull/3789
|
||||
* Enable FIPS by default. by @TingluoHuang in https://github.com/actions/runner/pull/3793
|
||||
* Support auth migration using authUrlV2 in Runner/MessageListener. by @TingluoHuang in https://github.com/actions/runner/pull/3787
|
||||
* Cleanup feature flag actions_skip_retry_complete_job_upon_known_errors by @ericsciple in https://github.com/actions/runner/pull/3806
|
||||
* Update dotnet sdk to latest version @8.0.408 by @github-actions in https://github.com/actions/runner/pull/3808
|
||||
* Bump hook to 0.7.0 by @nikola-jokic in https://github.com/actions/runner/pull/3813
|
||||
* Allow enable auth migration by default. by @TingluoHuang in https://github.com/actions/runner/pull/3804
|
||||
* Do not retry /renewjob on 404 by @ericsciple in https://github.com/actions/runner/pull/3828
|
||||
* Bump Microsoft.NET.Test.Sdk from 17.12.0 to 17.13.0 in /src by @dependabot in https://github.com/actions/runner/pull/3719
|
||||
* Add copilot-instructions.md by @pje in https://github.com/actions/runner/pull/3810
|
||||
* Bump actions/upload-release-asset from 1.0.1 to 1.0.2 by @dependabot in https://github.com/actions/runner/pull/3553
|
||||
* Ignore exception during auth migration. by @TingluoHuang in https://github.com/actions/runner/pull/3835
|
||||
* feat: default fromPath for problem matchers by @dsanders11 in https://github.com/actions/runner/pull/3802
|
||||
* Bump Azure.Storage.Blobs from 12.23.0 to 12.24.0 in /src by @dependabot in https://github.com/actions/runner/pull/3837
|
||||
* Bump nodejs version. by @TingluoHuang in https://github.com/actions/runner/pull/3840
|
||||
* Feature-flagged support for `JobContext.CheckRunID` by @pje in https://github.com/actions/runner/pull/3811
|
||||
* Bump System.ServiceProcess.ServiceController from 8.0.0 to 8.0.1 in /src by @dependabot in https://github.com/actions/runner/pull/3844
|
||||
* Bump xunit.runner.visualstudio from 2.5.8 to 2.8.2 in /src by @dependabot in https://github.com/actions/runner/pull/3845
|
||||
* Make sure the token's claims are match as expected. by @TingluoHuang in https://github.com/actions/runner/pull/3846
|
||||
* Prefer _migrated config on startup by @lokesh755 in https://github.com/actions/runner/pull/3853
|
||||
* Update docker and buildx by @TingluoHuang in https://github.com/actions/runner/pull/3854
|
||||
|
||||
- Adding Snapshot additional mapping tokens https://github.com/actions/runner/pull/3468
|
||||
- Create launch httpclient using the right handler and setting https://github.com/actions/runner/pull/3476
|
||||
- Fix missing default user-agent for jitconfig runner https://github.com/actions/runner/pull/3473
|
||||
- Cleanup back-compat code for interpreting Run Service status codes https://github.com/actions/runner/pull/3456
|
||||
- Add runner or worker to the useragent https://github.com/actions/runner/pull/3457
|
||||
- Handle Error Body in Responses from Broker https://github.com/actions/runner/pull/3454
|
||||
- Fix issues for composite actions (Run Service flow) https://github.com/actions/runner/pull/3446
|
||||
- Trace GitHub RequestId to log https://github.com/actions/runner/pull/3442
|
||||
- Add `jq`, `git`, `unzip` and `curl` to default packages installed https://github.com/actions/runner/pull/3056
|
||||
- Add pid to user-agent and session owner https://github.com/actions/runner/pull/3432
|
||||
## New Contributors
|
||||
* @dsanders11 made their first contribution in https://github.com/actions/runner/pull/3802
|
||||
|
||||
**Full Changelog**: https://github.com/actions/runner/compare/v2.319.1...v2.320.0
|
||||
**Full Changelog**: https://github.com/actions/runner/compare/v2.323.0...v2.324.0
|
||||
|
||||
_Note: Actions Runner follows a progressive release policy, so the latest release might not be available to your enterprise, organization, or repository yet.
|
||||
To confirm which version of the Actions Runner you should expect, please view the download instructions for your enterprise, organization, or repository.
|
||||
@@ -33,9 +53,7 @@ Add-Type -AssemblyName System.IO.Compression.FileSystem ;
|
||||
[System.IO.Compression.ZipFile]::ExtractToDirectory("$PWD\actions-runner-win-x64-<RUNNER_VERSION>.zip", "$PWD")
|
||||
```
|
||||
|
||||
## [Pre-release] Windows arm64
|
||||
|
||||
**Warning:** Windows arm64 runners are currently in preview status and use [unofficial versions of nodejs](https://unofficial-builds.nodejs.org/). They are not intended for production workflows.
|
||||
## Windows arm64
|
||||
|
||||
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.324.0
|
||||
|
||||
@@ -57,4 +57,13 @@
|
||||
<PropertyGroup>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Enable NuGet package auditing -->
|
||||
<NuGetAudit>true</NuGetAudit>
|
||||
<!-- Audit direct and transitive packages -->
|
||||
<NuGetAuditMode>all</NuGetAuditMode>
|
||||
<!-- Report low, moderate, high and critical advisories -->
|
||||
<NuGetAuditLevel>moderate</NuGetAuditLevel>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
1342
src/Misc/dotnet-install.ps1
vendored
1342
src/Misc/dotnet-install.ps1
vendored
File diff suppressed because it is too large
Load Diff
1222
src/Misc/dotnet-install.sh
vendored
1222
src/Misc/dotnet-install.sh
vendored
File diff suppressed because it is too large
Load Diff
824
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
824
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,6 @@
|
||||
"pack": "ncc build -o ../../layoutbin/hashFiles",
|
||||
"all": "npm run format && npm run lint && npm run build && npm run pack",
|
||||
"prepare": "cd ../../../../ && husky install"
|
||||
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -43,9 +42,9 @@
|
||||
"eslint": "^8.47.0",
|
||||
"eslint-plugin-github": "^4.10.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"prettier": "^3.0.3",
|
||||
"typescript": "^5.2.2",
|
||||
"husky": "^8.0.3",
|
||||
"lint-staged": "^14.0.0"
|
||||
"lint-staged": "^15.5.0",
|
||||
"prettier": "^3.0.3",
|
||||
"typescript": "^5.2.2"
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,10 @@ PACKAGERUNTIME=$1
|
||||
PRECACHE=$2
|
||||
|
||||
NODE_URL=https://nodejs.org/dist
|
||||
UNOFFICIAL_NODE_URL=https://unofficial-builds.nodejs.org/download/release
|
||||
NODE_ALPINE_URL=https://github.com/actions/alpine_nodejs/releases/download
|
||||
# When you update Node versions you must also create a new release of alpine_nodejs at that updated version.
|
||||
# Follow the instructions here: https://github.com/actions/alpine_nodejs?tab=readme-ov-file#getting-started
|
||||
NODE16_VERSION="16.20.2"
|
||||
NODE20_VERSION="20.13.1"
|
||||
NODE16_UNOFFICIAL_VERSION="16.20.0" # used only for win-arm64, remove node16 unofficial version when official version is available
|
||||
NODE20_VERSION="20.19.1"
|
||||
|
||||
get_abs_path() {
|
||||
# exploits the fact that pwd will print abs path when no args
|
||||
@@ -140,8 +137,6 @@ function acquireExternalTool() {
|
||||
|
||||
# Download the external tools only for Windows.
|
||||
if [[ "$PACKAGERUNTIME" == "win-x64" || "$PACKAGERUNTIME" == "win-x86" ]]; then
|
||||
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/$PACKAGERUNTIME/node.exe" node16/bin
|
||||
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/$PACKAGERUNTIME/node.lib" node16/bin
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.exe" node20/bin
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.lib" node20/bin
|
||||
if [[ "$PRECACHE" != "" ]]; then
|
||||
@@ -152,8 +147,6 @@ fi
|
||||
# Download the external tools only for Windows.
|
||||
if [[ "$PACKAGERUNTIME" == "win-arm64" ]]; then
|
||||
# todo: replace these with official release when available
|
||||
acquireExternalTool "$UNOFFICIAL_NODE_URL/v${NODE16_UNOFFICIAL_VERSION}/$PACKAGERUNTIME/node.exe" node16/bin
|
||||
acquireExternalTool "$UNOFFICIAL_NODE_URL/v${NODE16_UNOFFICIAL_VERSION}/$PACKAGERUNTIME/node.lib" node16/bin
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.exe" node20/bin
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.lib" node20/bin
|
||||
if [[ "$PRECACHE" != "" ]]; then
|
||||
@@ -163,30 +156,24 @@ fi
|
||||
|
||||
# Download the external tools only for OSX.
|
||||
if [[ "$PACKAGERUNTIME" == "osx-x64" ]]; then
|
||||
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-darwin-x64.tar.gz" node16 fix_nested_dir
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-darwin-x64.tar.gz" node20 fix_nested_dir
|
||||
fi
|
||||
|
||||
if [[ "$PACKAGERUNTIME" == "osx-arm64" ]]; then
|
||||
# node.js v12 doesn't support macOS on arm64.
|
||||
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-darwin-arm64.tar.gz" node16 fix_nested_dir
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-darwin-arm64.tar.gz" node20 fix_nested_dir
|
||||
fi
|
||||
|
||||
# Download the external tools for Linux PACKAGERUNTIMEs.
|
||||
if [[ "$PACKAGERUNTIME" == "linux-x64" ]]; then
|
||||
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-linux-x64.tar.gz" node16 fix_nested_dir
|
||||
acquireExternalTool "$NODE_ALPINE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-alpine-x64.tar.gz" node16_alpine
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-linux-x64.tar.gz" node20 fix_nested_dir
|
||||
acquireExternalTool "$NODE_ALPINE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-alpine-x64.tar.gz" node20_alpine
|
||||
fi
|
||||
|
||||
if [[ "$PACKAGERUNTIME" == "linux-arm64" ]]; then
|
||||
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-linux-arm64.tar.gz" node16 fix_nested_dir
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-linux-arm64.tar.gz" node20 fix_nested_dir
|
||||
fi
|
||||
|
||||
if [[ "$PACKAGERUNTIME" == "linux-arm" ]]; then
|
||||
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-linux-armv7l.tar.gz" node16 fix_nested_dir
|
||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-linux-armv7l.tar.gz" node20 fix_nested_dir
|
||||
fi
|
||||
|
||||
@@ -10,7 +10,7 @@ if [ -f ".path" ]; then
|
||||
echo ".path=${PATH}"
|
||||
fi
|
||||
|
||||
nodever=${GITHUB_ACTIONS_RUNNER_FORCED_NODE_VERSION:-node16}
|
||||
nodever="node20"
|
||||
|
||||
# insert anything to setup env when running as a service
|
||||
# run the host process which keep the listener alive
|
||||
|
||||
@@ -135,6 +135,10 @@ if [[ "$currentplatform" == 'darwin' && restartinteractiverunner -eq 0 ]]; then
|
||||
then
|
||||
# inspect the open file handles to find the node process
|
||||
# we can't actually inspect the process using ps because it uses relative paths and doesn't follow symlinks
|
||||
nodever="node20"
|
||||
path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-)
|
||||
if [[ $? -ne 0 || -z "$path" ]] # Fallback if RunnerService.js was started with node16
|
||||
then
|
||||
nodever="node16"
|
||||
path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-)
|
||||
if [[ $? -ne 0 || -z "$path" ]] # Fallback if RunnerService.js was started with node12
|
||||
@@ -142,6 +146,7 @@ if [[ "$currentplatform" == 'darwin' && restartinteractiverunner -eq 0 ]]; then
|
||||
nodever="node12"
|
||||
path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-)
|
||||
fi
|
||||
fi
|
||||
if [[ $? -eq 0 && -n "$path" ]]
|
||||
then
|
||||
# trim the last 5 characters of the path '/node'
|
||||
@@ -178,6 +183,19 @@ if [[ "$currentplatform" == 'darwin' && restartinteractiverunner -eq 0 ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# update runsvc.sh
|
||||
if [ -f "$rootfolder/runsvc.sh" ]
|
||||
then
|
||||
date "+[%F %T-%4N] Update runsvc.sh" >> "$logfile" 2>&1
|
||||
cat "$rootfolder/bin/runsvc.sh" > "$rootfolder/runsvc.sh"
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
date "+[%F %T-%4N] Can't update $rootfolder/runsvc.sh using $rootfolder/bin/runsvc.sh" >> "$logfile" 2>&1
|
||||
mv -fv "$logfile" "$logfile.failed"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
date "+[%F %T-%4N] Update succeed" >> "$logfile"
|
||||
|
||||
touch update.finished
|
||||
|
||||
13
src/Runner.Common/AuthMigration.cs
Normal file
13
src/Runner.Common/AuthMigration.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace GitHub.Runner.Common
|
||||
{
|
||||
public class AuthMigrationEventArgs : EventArgs
|
||||
{
|
||||
public AuthMigrationEventArgs(string trace)
|
||||
{
|
||||
Trace = trace;
|
||||
}
|
||||
public string Trace { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using GitHub.DistributedTask.Pipelines;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.WebApi;
|
||||
using Sdk.RSWebApi.Contracts;
|
||||
using Sdk.WebApi.WebApi.RawClient;
|
||||
|
||||
@@ -36,6 +37,7 @@ namespace GitHub.Runner.Common
|
||||
|
||||
public async Task ConnectAsync(Uri serverUri, VssCredentials credentials)
|
||||
{
|
||||
Trace.Entering();
|
||||
_brokerUri = serverUri;
|
||||
|
||||
_connection = VssUtil.CreateRawConnection(serverUri, credentials);
|
||||
@@ -86,13 +88,18 @@ namespace GitHub.Runner.Common
|
||||
}
|
||||
|
||||
public Task ForceRefreshConnection(VssCredentials credentials)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_brokerUri?.AbsoluteUri))
|
||||
{
|
||||
return ConnectAsync(_brokerUri, credentials);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public bool ShouldRetryException(Exception ex)
|
||||
{
|
||||
if (ex is AccessDeniedException ade)
|
||||
if (ex is AccessDeniedException || ex is RunnerNotFoundException || ex is HostedRunnerDeprovisionedException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -116,11 +116,15 @@ namespace GitHub.Runner.Common
|
||||
bool IsConfigured();
|
||||
bool IsServiceConfigured();
|
||||
bool HasCredentials();
|
||||
bool IsMigratedConfigured();
|
||||
CredentialData GetCredentials();
|
||||
CredentialData GetMigratedCredentials();
|
||||
RunnerSettings GetSettings();
|
||||
RunnerSettings GetMigratedSettings();
|
||||
void SaveCredential(CredentialData credential);
|
||||
void SaveMigratedCredential(CredentialData credential);
|
||||
void SaveSettings(RunnerSettings settings);
|
||||
void SaveMigratedSettings(RunnerSettings settings);
|
||||
void DeleteCredential();
|
||||
void DeleteMigratedCredential();
|
||||
void DeleteSettings();
|
||||
@@ -130,6 +134,7 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
private string _binPath;
|
||||
private string _configFilePath;
|
||||
private string _migratedConfigFilePath;
|
||||
private string _credFilePath;
|
||||
private string _migratedCredFilePath;
|
||||
private string _serviceConfigFilePath;
|
||||
@@ -137,6 +142,7 @@ namespace GitHub.Runner.Common
|
||||
private CredentialData _creds;
|
||||
private CredentialData _migratedCreds;
|
||||
private RunnerSettings _settings;
|
||||
private RunnerSettings _migratedSettings;
|
||||
|
||||
public override void Initialize(IHostContext hostContext)
|
||||
{
|
||||
@@ -154,6 +160,9 @@ namespace GitHub.Runner.Common
|
||||
_configFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Runner);
|
||||
Trace.Info("ConfigFilePath: {0}", _configFilePath);
|
||||
|
||||
_migratedConfigFilePath = hostContext.GetConfigFile(WellKnownConfigFile.MigratedRunner);
|
||||
Trace.Info("MigratedConfigFilePath: {0}", _migratedConfigFilePath);
|
||||
|
||||
_credFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Credentials);
|
||||
Trace.Info("CredFilePath: {0}", _credFilePath);
|
||||
|
||||
@@ -169,7 +178,7 @@ namespace GitHub.Runner.Common
|
||||
public bool HasCredentials()
|
||||
{
|
||||
Trace.Info("HasCredentials()");
|
||||
bool credsStored = (new FileInfo(_credFilePath)).Exists || (new FileInfo(_migratedCredFilePath)).Exists;
|
||||
bool credsStored = new FileInfo(_credFilePath).Exists || new FileInfo(_migratedCredFilePath).Exists;
|
||||
Trace.Info("stored {0}", credsStored);
|
||||
return credsStored;
|
||||
}
|
||||
@@ -177,7 +186,7 @@ namespace GitHub.Runner.Common
|
||||
public bool IsConfigured()
|
||||
{
|
||||
Trace.Info("IsConfigured()");
|
||||
bool configured = new FileInfo(_configFilePath).Exists;
|
||||
bool configured = new FileInfo(_configFilePath).Exists || new FileInfo(_migratedConfigFilePath).Exists;
|
||||
Trace.Info("IsConfigured: {0}", configured);
|
||||
return configured;
|
||||
}
|
||||
@@ -185,11 +194,19 @@ namespace GitHub.Runner.Common
|
||||
public bool IsServiceConfigured()
|
||||
{
|
||||
Trace.Info("IsServiceConfigured()");
|
||||
bool serviceConfigured = (new FileInfo(_serviceConfigFilePath)).Exists;
|
||||
bool serviceConfigured = new FileInfo(_serviceConfigFilePath).Exists;
|
||||
Trace.Info($"IsServiceConfigured: {serviceConfigured}");
|
||||
return serviceConfigured;
|
||||
}
|
||||
|
||||
public bool IsMigratedConfigured()
|
||||
{
|
||||
Trace.Info("IsMigratedConfigured()");
|
||||
bool configured = new FileInfo(_migratedConfigFilePath).Exists;
|
||||
Trace.Info("IsMigratedConfigured: {0}", configured);
|
||||
return configured;
|
||||
}
|
||||
|
||||
public CredentialData GetCredentials()
|
||||
{
|
||||
if (_creds == null)
|
||||
@@ -229,6 +246,25 @@ namespace GitHub.Runner.Common
|
||||
return _settings;
|
||||
}
|
||||
|
||||
public RunnerSettings GetMigratedSettings()
|
||||
{
|
||||
if (_migratedSettings == null)
|
||||
{
|
||||
RunnerSettings configuredSettings = null;
|
||||
if (File.Exists(_migratedConfigFilePath))
|
||||
{
|
||||
string json = File.ReadAllText(_migratedConfigFilePath, Encoding.UTF8);
|
||||
Trace.Info($"Read migrated setting file: {json.Length} chars");
|
||||
configuredSettings = StringUtil.ConvertFromJson<RunnerSettings>(json);
|
||||
}
|
||||
|
||||
ArgUtil.NotNull(configuredSettings, nameof(configuredSettings));
|
||||
_migratedSettings = configuredSettings;
|
||||
}
|
||||
|
||||
return _migratedSettings;
|
||||
}
|
||||
|
||||
public void SaveCredential(CredentialData credential)
|
||||
{
|
||||
Trace.Info("Saving {0} credential @ {1}", credential.Scheme, _credFilePath);
|
||||
@@ -244,6 +280,21 @@ namespace GitHub.Runner.Common
|
||||
File.SetAttributes(_credFilePath, File.GetAttributes(_credFilePath) | FileAttributes.Hidden);
|
||||
}
|
||||
|
||||
public void SaveMigratedCredential(CredentialData credential)
|
||||
{
|
||||
Trace.Info("Saving {0} migrated credential @ {1}", credential.Scheme, _migratedCredFilePath);
|
||||
if (File.Exists(_migratedCredFilePath))
|
||||
{
|
||||
// Delete existing credential file first, since the file is hidden and not able to overwrite.
|
||||
Trace.Info("Delete exist runner migrated credential file.");
|
||||
IOUtil.DeleteFile(_migratedCredFilePath);
|
||||
}
|
||||
|
||||
IOUtil.SaveObject(credential, _migratedCredFilePath);
|
||||
Trace.Info("Migrated Credentials Saved.");
|
||||
File.SetAttributes(_migratedCredFilePath, File.GetAttributes(_migratedCredFilePath) | FileAttributes.Hidden);
|
||||
}
|
||||
|
||||
public void SaveSettings(RunnerSettings settings)
|
||||
{
|
||||
Trace.Info("Saving runner settings.");
|
||||
@@ -259,6 +310,21 @@ namespace GitHub.Runner.Common
|
||||
File.SetAttributes(_configFilePath, File.GetAttributes(_configFilePath) | FileAttributes.Hidden);
|
||||
}
|
||||
|
||||
public void SaveMigratedSettings(RunnerSettings settings)
|
||||
{
|
||||
Trace.Info("Saving runner migrated settings");
|
||||
if (File.Exists(_migratedConfigFilePath))
|
||||
{
|
||||
// Delete existing settings file first, since the file is hidden and not able to overwrite.
|
||||
Trace.Info("Delete exist runner migrated settings file.");
|
||||
IOUtil.DeleteFile(_migratedConfigFilePath);
|
||||
}
|
||||
|
||||
IOUtil.SaveObject(settings, _migratedConfigFilePath);
|
||||
Trace.Info("Migrated Settings Saved.");
|
||||
File.SetAttributes(_migratedConfigFilePath, File.GetAttributes(_migratedConfigFilePath) | FileAttributes.Hidden);
|
||||
}
|
||||
|
||||
public void DeleteCredential()
|
||||
{
|
||||
IOUtil.Delete(_credFilePath, default(CancellationToken));
|
||||
@@ -273,6 +339,12 @@ namespace GitHub.Runner.Common
|
||||
public void DeleteSettings()
|
||||
{
|
||||
IOUtil.Delete(_configFilePath, default(CancellationToken));
|
||||
IOUtil.Delete(_migratedConfigFilePath, default(CancellationToken));
|
||||
}
|
||||
|
||||
public void DeleteMigratedSettings()
|
||||
{
|
||||
IOUtil.Delete(_migratedConfigFilePath, default(CancellationToken));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace GitHub.Runner.Common
|
||||
public enum WellKnownConfigFile
|
||||
{
|
||||
Runner,
|
||||
MigratedRunner,
|
||||
Credentials,
|
||||
MigratedCredentials,
|
||||
RSACredentials,
|
||||
@@ -154,15 +155,19 @@ namespace GitHub.Runner.Common
|
||||
public const int RunnerUpdating = 3;
|
||||
public const int RunOnceRunnerUpdating = 4;
|
||||
public const int SessionConflict = 5;
|
||||
// Temporary error code to indicate that the runner configuration has been refreshed
|
||||
// and the runner should be restarted. This is a temporary code and will be removed in the future after
|
||||
// the runner is migrated to runner admin.
|
||||
public const int RunnerConfigurationRefreshed = 6;
|
||||
}
|
||||
|
||||
public static class Features
|
||||
{
|
||||
public static readonly string DiskSpaceWarning = "runner.diskspace.warning";
|
||||
public static readonly string Node16Warning = "DistributedTask.AddWarningToNode16Action";
|
||||
public static readonly string LogTemplateErrorsAsDebugMessages = "DistributedTask.LogTemplateErrorsAsDebugMessages";
|
||||
public static readonly string UseContainerPathForTemplate = "DistributedTask.UseContainerPathForTemplate";
|
||||
public static readonly string AllowRunnerContainerHooks = "DistributedTask.AllowRunnerContainerHooks";
|
||||
public static readonly string AddCheckRunIdToJobContext = "actions_add_check_run_id_to_job_context";
|
||||
}
|
||||
|
||||
public static readonly string InternalTelemetryIssueDataKey = "_internal_telemetry";
|
||||
@@ -176,14 +181,6 @@ namespace GitHub.Runner.Common
|
||||
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 UnsupportedSummarySize = "$GITHUB_STEP_SUMMARY upload aborted, supports content up to a size of {0}k, got {1}k. For more information see: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summary";
|
||||
public static readonly string SummaryUploadError = "$GITHUB_STEP_SUMMARY upload aborted, an error occurred when uploading the summary. For more information see: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summary";
|
||||
public static readonly string DetectedNodeAfterEndOfLifeMessage = "Node.js 16 actions are deprecated. Please update the following actions to use Node.js 20: {0}. For more information see: https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20/.";
|
||||
public static readonly string DeprecatedNodeDetectedAfterEndOfLifeActions = "DeprecatedNodeActionsMessageWarnings";
|
||||
public static readonly string DeprecatedNodeVersion = "node16";
|
||||
public static readonly string EnforcedNode12DetectedAfterEndOfLife = "The following actions uses node12 which is deprecated and will be forced to run on node16: {0}. For more info: https://github.blog/changelog/2023-06-13-github-actions-all-actions-will-run-on-node16-instead-of-node12-by-default/";
|
||||
public static readonly string EnforcedNode12DetectedAfterEndOfLifeEnvVariable = "Node16ForceActionsWarnings";
|
||||
public static readonly string EnforcedNode16DetectedAfterEndOfLife = "The following actions use a deprecated Node.js version and will be forced to run on node20: {0}. For more info: https://github.blog/changelog/2024-03-07-github-actions-all-actions-will-run-on-node20-instead-of-node16-by-default/";
|
||||
public static readonly string EnforcedNode16DetectedAfterEndOfLifeEnvVariable = "Node20ForceActionsWarnings";
|
||||
|
||||
}
|
||||
|
||||
public static class RunnerEvent
|
||||
@@ -254,20 +251,17 @@ namespace GitHub.Runner.Common
|
||||
public static readonly string RequireJobContainer = "ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER";
|
||||
public static readonly string RunnerDebug = "ACTIONS_RUNNER_DEBUG";
|
||||
public static readonly string StepDebug = "ACTIONS_STEP_DEBUG";
|
||||
public static readonly string AllowActionsUseUnsecureNodeVersion = "ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION";
|
||||
public static readonly string ManualForceActionsToNode20 = "FORCE_JAVASCRIPT_ACTIONS_TO_NODE20";
|
||||
}
|
||||
|
||||
public static class Agent
|
||||
{
|
||||
public static readonly string ToolsDirectory = "agent.ToolsDirectory";
|
||||
|
||||
// Set this env var to "node12" to downgrade the node version for internal functions (e.g hashfiles). This does NOT affect the version of node actions.
|
||||
// Set this env var to "nodeXY" to downgrade the node version for internal functions (e.g hashfiles). This does NOT affect the version of node actions.
|
||||
public static readonly string ForcedInternalNodeVersion = "ACTIONS_RUNNER_FORCED_INTERNAL_NODE_VERSION";
|
||||
public static readonly string ForcedActionsNodeVersion = "ACTIONS_RUNNER_FORCE_ACTIONS_NODE_VERSION";
|
||||
public static readonly string PrintLogToStdout = "ACTIONS_RUNNER_PRINT_LOG_TO_STDOUT";
|
||||
public static readonly string ActionArchiveCacheDirectory = "ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE";
|
||||
public static readonly string ManualForceActionsToNode20 = "FORCE_JAVASCRIPT_ACTIONS_TO_NODE20";
|
||||
}
|
||||
|
||||
public static class System
|
||||
@@ -280,10 +274,6 @@ namespace GitHub.Runner.Common
|
||||
public static readonly string PhaseDisplayName = "system.phaseDisplayName";
|
||||
public static readonly string JobRequestType = "system.jobRequestType";
|
||||
public static readonly string OrchestrationId = "system.orchestrationId";
|
||||
public static readonly string TestDotNet8Compatibility = "system.testDotNet8Compatibility";
|
||||
public static readonly string DotNet8CompatibilityOutputLength = "system.dotNet8CompatibilityOutputLength";
|
||||
public static readonly string DotNet8CompatibilityOutputPattern = "system.dotNet8CompatibilityOutputPattern";
|
||||
public static readonly string DotNet8CompatibilityWarning = "system.dotNet8CompatibilityWarning";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,11 @@ namespace GitHub.Runner.Common
|
||||
void ShutdownRunner(ShutdownReason reason);
|
||||
void WritePerfCounter(string counter);
|
||||
void LoadDefaultUserAgents();
|
||||
|
||||
bool AllowAuthMigration { get; }
|
||||
void EnableAuthMigration(string trace);
|
||||
void DeferAuthMigration(TimeSpan deferred, string trace);
|
||||
event EventHandler<AuthMigrationEventArgs> AuthMigrationChanged;
|
||||
}
|
||||
|
||||
public enum StartupType
|
||||
@@ -70,12 +75,21 @@ namespace GitHub.Runner.Common
|
||||
private RunnerWebProxy _webProxy = new();
|
||||
private string _hostType = string.Empty;
|
||||
|
||||
// disable auth migration by default
|
||||
private readonly ManualResetEventSlim _allowAuthMigration = new ManualResetEventSlim(false);
|
||||
private DateTime _deferredAuthMigrationTime = DateTime.MaxValue;
|
||||
private readonly object _authMigrationLock = new object();
|
||||
private CancellationTokenSource _authMigrationAutoReenableTaskCancellationTokenSource = new();
|
||||
private Task _authMigrationAutoReenableTask;
|
||||
|
||||
public event EventHandler Unloading;
|
||||
public event EventHandler<AuthMigrationEventArgs> AuthMigrationChanged;
|
||||
public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token;
|
||||
public ShutdownReason RunnerShutdownReason { get; private set; }
|
||||
public ISecretMasker SecretMasker => _secretMasker;
|
||||
public List<ProductInfoHeaderValue> UserAgents => _userAgents;
|
||||
public RunnerWebProxy WebProxy => _webProxy;
|
||||
public bool AllowAuthMigration => _allowAuthMigration.IsSet;
|
||||
public HostContext(string hostType, string logFile = null)
|
||||
{
|
||||
// Validate args.
|
||||
@@ -207,6 +221,71 @@ namespace GitHub.Runner.Common
|
||||
LoadDefaultUserAgents();
|
||||
}
|
||||
|
||||
// marked as internal for testing
|
||||
internal async Task AuthMigrationAuthReenableAsync(TimeSpan refreshInterval, CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
_trace.Verbose($"Auth migration defer timer is set to expire at {_deferredAuthMigrationTime.ToString("O")}. AllowAuthMigration: {_allowAuthMigration.IsSet}.");
|
||||
await Task.Delay(refreshInterval, token);
|
||||
if (!_allowAuthMigration.IsSet && DateTime.UtcNow > _deferredAuthMigrationTime)
|
||||
{
|
||||
_trace.Info($"Auth migration defer timer expired. Allowing auth migration.");
|
||||
EnableAuthMigration("Auth migration defer timer expired.");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// Task was cancelled, exit the loop.
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_trace.Info("Error in auth migration reenable task.");
|
||||
_trace.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void EnableAuthMigration(string trace)
|
||||
{
|
||||
_allowAuthMigration.Set();
|
||||
|
||||
lock (_authMigrationLock)
|
||||
{
|
||||
if (_authMigrationAutoReenableTask == null)
|
||||
{
|
||||
var refreshIntervalInMS = 60 * 1000;
|
||||
#if DEBUG
|
||||
// For L0, we will refresh faster
|
||||
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GITHUB_ACTION_AUTH_MIGRATION_REFRESH_INTERVAL")))
|
||||
{
|
||||
refreshIntervalInMS = int.Parse(Environment.GetEnvironmentVariable("_GITHUB_ACTION_AUTH_MIGRATION_REFRESH_INTERVAL"));
|
||||
}
|
||||
#endif
|
||||
_authMigrationAutoReenableTask = AuthMigrationAuthReenableAsync(TimeSpan.FromMilliseconds(refreshIntervalInMS), _authMigrationAutoReenableTaskCancellationTokenSource.Token);
|
||||
}
|
||||
}
|
||||
|
||||
_trace.Info($"Enable auth migration at {DateTime.UtcNow.ToString("O")}.");
|
||||
AuthMigrationChanged?.Invoke(this, new AuthMigrationEventArgs(trace));
|
||||
}
|
||||
|
||||
public void DeferAuthMigration(TimeSpan deferred, string trace)
|
||||
{
|
||||
_allowAuthMigration.Reset();
|
||||
|
||||
// defer migration for a while
|
||||
lock (_authMigrationLock)
|
||||
{
|
||||
_deferredAuthMigrationTime = DateTime.UtcNow.Add(deferred);
|
||||
}
|
||||
|
||||
_trace.Info($"Disabled auth migration until {_deferredAuthMigrationTime.ToString("O")}.");
|
||||
AuthMigrationChanged?.Invoke(this, new AuthMigrationEventArgs(trace));
|
||||
}
|
||||
|
||||
public void LoadDefaultUserAgents()
|
||||
{
|
||||
if (string.IsNullOrEmpty(WebProxy.HttpProxyAddress) && string.IsNullOrEmpty(WebProxy.HttpsProxyAddress))
|
||||
@@ -343,6 +422,12 @@ namespace GitHub.Runner.Common
|
||||
".runner");
|
||||
break;
|
||||
|
||||
case WellKnownConfigFile.MigratedRunner:
|
||||
path = Path.Combine(
|
||||
GetDirectory(WellKnownDirectory.Root),
|
||||
".runner_migrated");
|
||||
break;
|
||||
|
||||
case WellKnownConfigFile.Credentials:
|
||||
path = Path.Combine(
|
||||
GetDirectory(WellKnownDirectory.Root),
|
||||
@@ -543,6 +628,18 @@ namespace GitHub.Runner.Common
|
||||
_loadContext.Unloading -= LoadContext_Unloading;
|
||||
_loadContext = null;
|
||||
}
|
||||
|
||||
if (_authMigrationAutoReenableTask != null)
|
||||
{
|
||||
_authMigrationAutoReenableTaskCancellationTokenSource?.Cancel();
|
||||
}
|
||||
|
||||
if (_authMigrationAutoReenableTaskCancellationTokenSource != null)
|
||||
{
|
||||
_authMigrationAutoReenableTaskCancellationTokenSource?.Dispose();
|
||||
_authMigrationAutoReenableTaskCancellationTokenSource = null;
|
||||
}
|
||||
|
||||
_httpTraceSubscription?.Dispose();
|
||||
_diagListenerSubscription?.Dispose();
|
||||
_traceManager?.Dispose();
|
||||
@@ -629,7 +726,7 @@ namespace GitHub.Runner.Common
|
||||
payload[0] = Enum.Parse(typeof(GitHub.Services.Common.VssCredentialsType), ((int)payload[0]).ToString());
|
||||
}
|
||||
|
||||
if (payload.Length > 0)
|
||||
if (payload.Length > 0 && !string.IsNullOrEmpty(eventData.Message))
|
||||
{
|
||||
message = String.Format(eventData.Message.Replace("%n", Environment.NewLine), payload);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
Task ConnectAsync(Uri serverUrl, VssCredentials credentials);
|
||||
|
||||
Task<AgentJobRequestMessage> GetJobMessageAsync(string id, CancellationToken token);
|
||||
Task<AgentJobRequestMessage> GetJobMessageAsync(string id, string billingOwnerId, CancellationToken token);
|
||||
|
||||
Task CompleteJobAsync(
|
||||
Guid planId,
|
||||
@@ -28,6 +28,8 @@ namespace GitHub.Runner.Common
|
||||
IList<StepResult> stepResults,
|
||||
IList<Annotation> jobAnnotations,
|
||||
string environmentUrl,
|
||||
IList<Telemetry> telemetry,
|
||||
string billingOwnerId,
|
||||
CancellationToken token);
|
||||
|
||||
Task<RenewJobResponse> RenewJobAsync(Guid planId, Guid jobId, CancellationToken token);
|
||||
@@ -57,11 +59,11 @@ namespace GitHub.Runner.Common
|
||||
}
|
||||
}
|
||||
|
||||
public Task<AgentJobRequestMessage> GetJobMessageAsync(string id, CancellationToken cancellationToken)
|
||||
public Task<AgentJobRequestMessage> GetJobMessageAsync(string id, string billingOwnerId, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckConnection();
|
||||
return RetryRequest<AgentJobRequestMessage>(
|
||||
async () => await _runServiceHttpClient.GetJobMessageAsync(requestUri, id, VarUtil.OS, cancellationToken), cancellationToken,
|
||||
async () => await _runServiceHttpClient.GetJobMessageAsync(requestUri, id, VarUtil.OS, billingOwnerId, cancellationToken), cancellationToken,
|
||||
shouldRetry: ex =>
|
||||
ex is not TaskOrchestrationJobNotFoundException && // HTTP status 404
|
||||
ex is not TaskOrchestrationJobAlreadyAcquiredException && // HTTP status 409
|
||||
@@ -76,18 +78,25 @@ namespace GitHub.Runner.Common
|
||||
IList<StepResult> stepResults,
|
||||
IList<Annotation> jobAnnotations,
|
||||
string environmentUrl,
|
||||
IList<Telemetry> telemetry,
|
||||
string billingOwnerId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
CheckConnection();
|
||||
return RetryRequest(
|
||||
async () => await _runServiceHttpClient.CompleteJobAsync(requestUri, planId, jobId, result, outputs, stepResults, jobAnnotations, environmentUrl, cancellationToken), cancellationToken);
|
||||
async () => await _runServiceHttpClient.CompleteJobAsync(requestUri, planId, jobId, result, outputs, stepResults, jobAnnotations, environmentUrl, telemetry, billingOwnerId, cancellationToken), cancellationToken,
|
||||
shouldRetry: ex =>
|
||||
ex is not VssUnauthorizedException && // HTTP status 401
|
||||
ex is not TaskOrchestrationJobNotFoundException); // HTTP status 404
|
||||
}
|
||||
|
||||
public Task<RenewJobResponse> RenewJobAsync(Guid planId, Guid jobId, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckConnection();
|
||||
return RetryRequest<RenewJobResponse>(
|
||||
async () => await _runServiceHttpClient.RenewJobAsync(requestUri, planId, jobId, cancellationToken), cancellationToken);
|
||||
async () => await _runServiceHttpClient.RenewJobAsync(requestUri, planId, jobId, cancellationToken), cancellationToken,
|
||||
shouldRetry: ex =>
|
||||
ex is not TaskOrchestrationJobNotFoundException); // HTTP status 404
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<OutputType>Library</OutputType>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64;win-arm64</RuntimeIdentifiers>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<NoWarn>NU1701;NU1603</NoWarn>
|
||||
<NoWarn>NU1701;NU1603;SYSLIB0050;SYSLIB0051</NoWarn>
|
||||
<Version>$(Version)</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.4.0" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="4.4.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
|
||||
@@ -46,7 +46,11 @@ namespace GitHub.Runner.Common
|
||||
var githubApiUrl = "";
|
||||
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
||||
var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (path.Length == 1)
|
||||
var isOrgRunner = path.Length == 1;
|
||||
var isRepoOrEnterpriseRunner = path.Length == 2;
|
||||
var isRepoRunner = isRepoOrEnterpriseRunner && !string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (isOrgRunner)
|
||||
{
|
||||
// org runner
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
@@ -58,14 +62,23 @@ namespace GitHub.Runner.Common
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/orgs/{path[0]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
||||
}
|
||||
}
|
||||
else if (path.Length == 2)
|
||||
else if (isRepoOrEnterpriseRunner)
|
||||
{
|
||||
// repo or enterprise runner.
|
||||
if (!string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase))
|
||||
// Repository runner
|
||||
if (isRepoRunner)
|
||||
{
|
||||
return null;
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/repos/{path[0]}/{path[1]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/repos/{path[0]}/{path[1]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Enterprise runner
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/{path[0]}/{path[1]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
||||
@@ -75,6 +88,7 @@ namespace GitHub.Runner.Common
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/{path[0]}/{path[1]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"'{githubUrl}' should point to an org or enterprise.");
|
||||
@@ -90,7 +104,11 @@ namespace GitHub.Runner.Common
|
||||
var githubApiUrl = "";
|
||||
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
||||
var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (path.Length == 1)
|
||||
var isOrgRunner = path.Length == 1;
|
||||
var isRepoOrEnterpriseRunner = path.Length == 2;
|
||||
var isRepoRunner = isRepoOrEnterpriseRunner && !string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (isOrgRunner)
|
||||
{
|
||||
// org runner
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
@@ -102,14 +120,23 @@ namespace GitHub.Runner.Common
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/orgs/{path[0]}/actions/runner-groups";
|
||||
}
|
||||
}
|
||||
else if (path.Length == 2)
|
||||
else if (isRepoOrEnterpriseRunner)
|
||||
{
|
||||
// repo or enterprise runner.
|
||||
if (!string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase))
|
||||
// Repository Runner
|
||||
if (isRepoRunner)
|
||||
{
|
||||
return null;
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/repos/{path[0]}/{path[1]}/actions/runner-groups";
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/repos/{path[0]}/{path[1]}/actions/runner-groups";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Enterprise Runner
|
||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||
{
|
||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/{path[0]}/{path[1]}/actions/runner-groups";
|
||||
@@ -119,6 +146,7 @@ namespace GitHub.Runner.Common
|
||||
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.");
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Services.WebApi;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.WebApi;
|
||||
|
||||
namespace GitHub.Runner.Common
|
||||
{
|
||||
@@ -50,7 +50,10 @@ namespace GitHub.Runner.Common
|
||||
Task<PackageMetadata> GetPackageAsync(string packageType, string platform, string version, bool includeToken, CancellationToken cancellationToken);
|
||||
|
||||
// agent update
|
||||
Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, ulong agentId, string currentState, string trace);
|
||||
Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, ulong agentId, string currentState, string trace, CancellationToken cancellationToken = default);
|
||||
|
||||
// runner config refresh
|
||||
Task<string> RefreshRunnerConfigAsync(int agentId, string configType, string encodedRunnerConfig, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public sealed class RunnerServer : RunnerService, IRunnerServer
|
||||
@@ -315,10 +318,17 @@ namespace GitHub.Runner.Common
|
||||
return _genericTaskAgentClient.GetPackageAsync(packageType, platform, version, includeToken, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, ulong agentId, string currentState, string trace)
|
||||
public Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, ulong agentId, string currentState, string trace, CancellationToken cancellationToken = default)
|
||||
{
|
||||
CheckConnection(RunnerConnectionType.Generic);
|
||||
return _genericTaskAgentClient.UpdateAgentUpdateStateAsync(agentPoolId, agentId, currentState, trace);
|
||||
return _genericTaskAgentClient.UpdateAgentUpdateStateAsync(agentPoolId, agentId, currentState, trace, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
// runner config refresh
|
||||
public Task<string> RefreshRunnerConfigAsync(int agentId, string configType, string encodedRunnerConfig, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckConnection(RunnerConnectionType.Generic);
|
||||
return _genericTaskAgentClient.RefreshRunnerConfigAsync(agentId, configType, encodedRunnerConfig, cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,8 @@ namespace GitHub.Runner.Common
|
||||
|
||||
protected async Task RetryRequest(Func<Task> func,
|
||||
CancellationToken cancellationToken,
|
||||
int maxRetryAttemptsCount = 5
|
||||
int maxRetryAttemptsCount = 5,
|
||||
Func<Exception, bool> shouldRetry = null
|
||||
)
|
||||
{
|
||||
async Task<Unit> wrappedFunc()
|
||||
@@ -78,7 +79,7 @@ namespace GitHub.Runner.Common
|
||||
await func();
|
||||
return Unit.Value;
|
||||
}
|
||||
await RetryRequest<Unit>(wrappedFunc, cancellationToken, maxRetryAttemptsCount);
|
||||
await RetryRequest<Unit>(wrappedFunc, cancellationToken, maxRetryAttemptsCount, shouldRetry);
|
||||
}
|
||||
|
||||
protected async Task<T> RetryRequest<T>(Func<Task<T>> func,
|
||||
|
||||
@@ -5,8 +5,8 @@ namespace GitHub.Runner.Common.Util
|
||||
{
|
||||
public static class NodeUtil
|
||||
{
|
||||
private const string _defaultNodeVersion = "node16";
|
||||
public static readonly ReadOnlyCollection<string> BuiltInNodeVersions = new(new[] { "node16", "node20" });
|
||||
private const string _defaultNodeVersion = "node20";
|
||||
public static readonly ReadOnlyCollection<string> BuiltInNodeVersions = new(new[] { "node20" });
|
||||
public static string GetInternalNodeVersion()
|
||||
{
|
||||
var forcedInternalNodeVersion = Environment.GetEnvironmentVariable(Constants.Variables.Agent.ForcedInternalNodeVersion);
|
||||
|
||||
@@ -9,11 +9,12 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Listener.Configuration;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Services.OAuth;
|
||||
using GitHub.Services.WebApi;
|
||||
|
||||
namespace GitHub.Runner.Listener
|
||||
{
|
||||
@@ -25,31 +26,64 @@ namespace GitHub.Runner.Listener
|
||||
private TaskAgentStatus runnerStatus = TaskAgentStatus.Online;
|
||||
private CancellationTokenSource _getMessagesTokenSource;
|
||||
private VssCredentials _creds;
|
||||
private VssCredentials _credsV2;
|
||||
private TaskAgentSession _session;
|
||||
private IRunnerServer _runnerServer;
|
||||
private IBrokerServer _brokerServer;
|
||||
private ICredentialManager _credMgr;
|
||||
private readonly Dictionary<string, int> _sessionCreationExceptionTracker = new();
|
||||
private bool _accessTokenRevoked = false;
|
||||
private readonly TimeSpan _sessionCreationRetryInterval = TimeSpan.FromSeconds(30);
|
||||
private readonly TimeSpan _sessionConflictRetryLimit = TimeSpan.FromMinutes(4);
|
||||
private readonly TimeSpan _clockSkewRetryLimit = TimeSpan.FromMinutes(30);
|
||||
private bool _needRefreshCredsV2 = false;
|
||||
private bool _handlerInitialized = false;
|
||||
private bool _isMigratedSettings = false;
|
||||
private const int _maxMigratedSettingsRetries = 3;
|
||||
private int _migratedSettingsRetryCount = 0;
|
||||
|
||||
public BrokerMessageListener()
|
||||
{
|
||||
}
|
||||
|
||||
public BrokerMessageListener(RunnerSettings settings, bool isMigratedSettings = false)
|
||||
{
|
||||
_settings = settings;
|
||||
_isMigratedSettings = isMigratedSettings;
|
||||
}
|
||||
|
||||
public override void Initialize(IHostContext hostContext)
|
||||
{
|
||||
base.Initialize(hostContext);
|
||||
|
||||
_term = HostContext.GetService<ITerminal>();
|
||||
_runnerServer = HostContext.GetService<IRunnerServer>();
|
||||
_brokerServer = HostContext.GetService<IBrokerServer>();
|
||||
_credMgr = HostContext.GetService<ICredentialManager>();
|
||||
}
|
||||
|
||||
public async Task<CreateSessionResult> CreateSessionAsync(CancellationToken token)
|
||||
{
|
||||
Trace.Entering();
|
||||
|
||||
// Settings
|
||||
// Load settings if not provided through constructor
|
||||
if (_settings == null)
|
||||
{
|
||||
var configManager = HostContext.GetService<IConfigurationManager>();
|
||||
_settings = configManager.LoadSettings();
|
||||
var serverUrl = _settings.ServerUrlV2;
|
||||
Trace.Info("Settings loaded from config manager");
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace.Info("Using provided settings");
|
||||
if (_isMigratedSettings)
|
||||
{
|
||||
Trace.Info("Using migrated settings from .runner_migrated");
|
||||
}
|
||||
}
|
||||
|
||||
var serverUrlV2 = _settings.ServerUrlV2;
|
||||
var serverUrl = _settings.ServerUrl;
|
||||
Trace.Info(_settings);
|
||||
|
||||
if (string.IsNullOrEmpty(_settings.ServerUrlV2))
|
||||
@@ -59,8 +93,7 @@ namespace GitHub.Runner.Listener
|
||||
|
||||
// Create connection.
|
||||
Trace.Info("Loading Credentials");
|
||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
||||
_creds = credMgr.LoadCredentials();
|
||||
_creds = _credMgr.LoadCredentials(allowAuthUrlV2: false);
|
||||
|
||||
var agent = new TaskAgentReference
|
||||
{
|
||||
@@ -83,9 +116,18 @@ namespace GitHub.Runner.Listener
|
||||
try
|
||||
{
|
||||
Trace.Info("Connecting to the Broker Server...");
|
||||
await _brokerServer.ConnectAsync(new Uri(serverUrl), _creds);
|
||||
_credsV2 = _credMgr.LoadCredentials(allowAuthUrlV2: true);
|
||||
await _brokerServer.ConnectAsync(new Uri(serverUrlV2), _credsV2);
|
||||
Trace.Info("VssConnection created");
|
||||
|
||||
if (!string.IsNullOrEmpty(serverUrl) &&
|
||||
!string.Equals(serverUrl, serverUrlV2, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Trace.Info("Connecting to the Runner server...");
|
||||
await _runnerServer.ConnectAsync(new Uri(serverUrl), _creds);
|
||||
Trace.Info("VssConnection created");
|
||||
}
|
||||
|
||||
_term.WriteLine();
|
||||
_term.WriteSuccessMessage("Connected to GitHub");
|
||||
_term.WriteLine();
|
||||
@@ -100,6 +142,13 @@ namespace GitHub.Runner.Listener
|
||||
encounteringError = false;
|
||||
}
|
||||
|
||||
if (!_handlerInitialized)
|
||||
{
|
||||
// Register event handler for auth migration state change
|
||||
HostContext.AuthMigrationChanged += HandleAuthMigrationChanged;
|
||||
_handlerInitialized = true;
|
||||
}
|
||||
|
||||
return CreateSessionResult.Success;
|
||||
}
|
||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||
@@ -118,7 +167,22 @@ namespace GitHub.Runner.Listener
|
||||
Trace.Error("Catch exception during create session.");
|
||||
Trace.Error(ex);
|
||||
|
||||
if (ex is VssOAuthTokenRequestException vssOAuthEx && _creds.Federated is VssOAuthCredential vssOAuthCred)
|
||||
// If using migrated settings, limit the number of retries before returning failure
|
||||
if (_isMigratedSettings)
|
||||
{
|
||||
_migratedSettingsRetryCount++;
|
||||
Trace.Warning($"Migrated settings retry {_migratedSettingsRetryCount} of {_maxMigratedSettingsRetries}");
|
||||
|
||||
if (_migratedSettingsRetryCount >= _maxMigratedSettingsRetries)
|
||||
{
|
||||
Trace.Warning("Reached maximum retry attempts for migrated settings. Returning failure to try default settings.");
|
||||
return CreateSessionResult.Failure;
|
||||
}
|
||||
}
|
||||
|
||||
if (!HostContext.AllowAuthMigration &&
|
||||
ex is VssOAuthTokenRequestException vssOAuthEx &&
|
||||
_credsV2.Federated is VssOAuthCredential vssOAuthCred)
|
||||
{
|
||||
// "invalid_client" means the runner registration has been deleted from the server.
|
||||
if (string.Equals(vssOAuthEx.Error, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
||||
@@ -130,7 +194,7 @@ namespace GitHub.Runner.Listener
|
||||
// Check whether we get 401 because the runner registration already removed by the service.
|
||||
// If the runner registration get deleted, we can't exchange oauth token.
|
||||
Trace.Error("Test oauth app registration.");
|
||||
var oauthTokenProvider = new VssOAuthTokenProvider(vssOAuthCred, new Uri(serverUrl));
|
||||
var oauthTokenProvider = new VssOAuthTokenProvider(vssOAuthCred, new Uri(serverUrlV2));
|
||||
var authError = await oauthTokenProvider.ValidateCredentialAsync(token);
|
||||
if (string.Equals(authError, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -139,7 +203,8 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsSessionCreationExceptionRetriable(ex))
|
||||
if (!HostContext.AllowAuthMigration &&
|
||||
!IsSessionCreationExceptionRetriable(ex))
|
||||
{
|
||||
_term.WriteError($"Failed to create session. {ex.Message}");
|
||||
if (ex is TaskAgentSessionConflictException)
|
||||
@@ -149,6 +214,12 @@ namespace GitHub.Runner.Listener
|
||||
return CreateSessionResult.Failure;
|
||||
}
|
||||
|
||||
if (HostContext.AllowAuthMigration)
|
||||
{
|
||||
Trace.Info("Disable migration mode for 60 minutes.");
|
||||
HostContext.DeferAuthMigration(TimeSpan.FromMinutes(60), $"Session creation failed with exception: {ex}");
|
||||
}
|
||||
|
||||
if (!encounteringError) //print the message only on the first error
|
||||
{
|
||||
_term.WriteError($"{DateTime.UtcNow:u}: Runner connect error: {ex.Message}. Retrying until reconnected.");
|
||||
@@ -165,6 +236,11 @@ namespace GitHub.Runner.Listener
|
||||
{
|
||||
if (_session != null && _session.SessionId != Guid.Empty)
|
||||
{
|
||||
if (_handlerInitialized)
|
||||
{
|
||||
HostContext.AuthMigrationChanged -= HandleAuthMigrationChanged;
|
||||
}
|
||||
|
||||
if (!_accessTokenRevoked)
|
||||
{
|
||||
using (var ts = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
|
||||
@@ -207,6 +283,13 @@ namespace GitHub.Runner.Listener
|
||||
_getMessagesTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
|
||||
try
|
||||
{
|
||||
if (_needRefreshCredsV2)
|
||||
{
|
||||
Trace.Info("Refreshing broker connection.");
|
||||
await RefreshBrokerConnectionAsync();
|
||||
_needRefreshCredsV2 = false;
|
||||
}
|
||||
|
||||
message = await _brokerServer.GetRunnerMessageAsync(_session.SessionId,
|
||||
runnerStatus,
|
||||
BuildConstants.RunnerPackage.Version,
|
||||
@@ -237,7 +320,16 @@ namespace GitHub.Runner.Listener
|
||||
Trace.Info("Runner OAuth token has been revoked. Unable to pull message.");
|
||||
throw;
|
||||
}
|
||||
catch (AccessDeniedException e) when (e.ErrorCode == 1)
|
||||
catch (HostedRunnerDeprovisionedException)
|
||||
{
|
||||
Trace.Info("Hosted runner has been deprovisioned.");
|
||||
throw;
|
||||
}
|
||||
catch (AccessDeniedException e) when (e.ErrorCode == 1 && !HostContext.AllowAuthMigration)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (RunnerNotFoundException) when (!HostContext.AllowAuthMigration)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
@@ -246,7 +338,8 @@ namespace GitHub.Runner.Listener
|
||||
Trace.Error("Catch exception during get next message.");
|
||||
Trace.Error(ex);
|
||||
|
||||
if (!IsGetNextMessageExceptionRetriable(ex))
|
||||
if (!HostContext.AllowAuthMigration &&
|
||||
!IsGetNextMessageExceptionRetriable(ex))
|
||||
{
|
||||
throw new NonRetryableException("Get next message failed with non-retryable error.", ex);
|
||||
}
|
||||
@@ -277,6 +370,12 @@ namespace GitHub.Runner.Listener
|
||||
encounteringError = true;
|
||||
}
|
||||
|
||||
if (HostContext.AllowAuthMigration)
|
||||
{
|
||||
Trace.Info("Disable migration mode for 60 minutes.");
|
||||
HostContext.DeferAuthMigration(TimeSpan.FromMinutes(60), $"Get next message failed with exception: {ex}");
|
||||
}
|
||||
|
||||
// re-create VssConnection before next retry
|
||||
await RefreshBrokerConnectionAsync();
|
||||
|
||||
@@ -308,7 +407,7 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RefreshListenerTokenAsync(CancellationToken cancellationToken)
|
||||
public async Task RefreshListenerTokenAsync()
|
||||
{
|
||||
await RefreshBrokerConnectionAsync();
|
||||
}
|
||||
@@ -324,6 +423,7 @@ namespace GitHub.Runner.Listener
|
||||
ex is TaskAgentPoolNotFoundException ||
|
||||
ex is TaskAgentSessionExpiredException ||
|
||||
ex is AccessDeniedException ||
|
||||
ex is RunnerNotFoundException ||
|
||||
ex is VssUnauthorizedException)
|
||||
{
|
||||
Trace.Info($"Non-retriable exception: {ex.Message}");
|
||||
@@ -410,17 +510,16 @@ namespace GitHub.Runner.Listener
|
||||
|
||||
private async Task RefreshBrokerConnectionAsync()
|
||||
{
|
||||
var configManager = HostContext.GetService<IConfigurationManager>();
|
||||
_settings = configManager.LoadSettings();
|
||||
Trace.Info("Reload credentials.");
|
||||
_credsV2 = _credMgr.LoadCredentials(allowAuthUrlV2: true);
|
||||
await _brokerServer.ConnectAsync(new Uri(_settings.ServerUrlV2), _credsV2);
|
||||
Trace.Info("Connection to Broker Server recreated.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(_settings.ServerUrlV2))
|
||||
private void HandleAuthMigrationChanged(object sender, EventArgs e)
|
||||
{
|
||||
throw new InvalidOperationException("ServerUrlV2 is not set");
|
||||
}
|
||||
|
||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
||||
VssCredentials creds = credMgr.LoadCredentials();
|
||||
await _brokerServer.ConnectAsync(new Uri(_settings.ServerUrlV2), creds);
|
||||
Trace.Info($"Auth migration changed. Current allow auth migration state: {HostContext.AllowAuthMigration}");
|
||||
_needRefreshCredsV2 = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
Task UnconfigureAsync(CommandSettings command);
|
||||
void DeleteLocalRunnerConfig();
|
||||
RunnerSettings LoadSettings();
|
||||
RunnerSettings LoadMigratedSettings();
|
||||
}
|
||||
|
||||
public sealed class ConfigurationManager : RunnerService, IConfigurationManager
|
||||
@@ -66,6 +67,22 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
return settings;
|
||||
}
|
||||
|
||||
public RunnerSettings LoadMigratedSettings()
|
||||
{
|
||||
Trace.Info(nameof(LoadMigratedSettings));
|
||||
|
||||
// Check if migrated settings file exists
|
||||
if (!_store.IsMigratedConfigured())
|
||||
{
|
||||
throw new NonRetryableException("No migrated configuration found.");
|
||||
}
|
||||
|
||||
RunnerSettings settings = _store.GetMigratedSettings();
|
||||
Trace.Info("Migrated Settings Loaded");
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
public async Task ConfigureAsync(CommandSettings command)
|
||||
{
|
||||
_term.WriteLine();
|
||||
@@ -127,7 +144,7 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
runnerSettings.ServerUrl = inputUrl;
|
||||
// Get the credentials
|
||||
credProvider = GetCredentialProvider(command, runnerSettings.ServerUrl);
|
||||
creds = credProvider.GetVssCredentials(HostContext);
|
||||
creds = credProvider.GetVssCredentials(HostContext, allowAuthUrlV2: false);
|
||||
Trace.Info("legacy vss cred retrieved");
|
||||
}
|
||||
else
|
||||
@@ -366,7 +383,7 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
{
|
||||
{ "clientId", agent.Authorization.ClientId.ToString("D") },
|
||||
{ "authorizationUrl", agent.Authorization.AuthorizationUrl.AbsoluteUri },
|
||||
{ "requireFipsCryptography", agent.Properties.GetValue("RequireFipsCryptography", false).ToString() }
|
||||
{ "requireFipsCryptography", agent.Properties.GetValue("RequireFipsCryptography", true).ToString() }
|
||||
},
|
||||
};
|
||||
|
||||
@@ -384,7 +401,7 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
if (!runnerSettings.UseV2Flow)
|
||||
{
|
||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
||||
VssCredentials credential = credMgr.LoadCredentials();
|
||||
VssCredentials credential = credMgr.LoadCredentials(allowAuthUrlV2: false);
|
||||
try
|
||||
{
|
||||
await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), credential);
|
||||
@@ -404,6 +421,20 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
}
|
||||
}
|
||||
|
||||
// allow the server to override the serverUrlV2 and useV2Flow
|
||||
if (agent.Properties.TryGetValue("ServerUrlV2", out string serverUrlV2) &&
|
||||
!string.IsNullOrEmpty(serverUrlV2))
|
||||
{
|
||||
Trace.Info($"Service enforced serverUrlV2: {serverUrlV2}");
|
||||
runnerSettings.ServerUrlV2 = serverUrlV2;
|
||||
}
|
||||
|
||||
if (agent.Properties.TryGetValue("UseV2Flow", out bool useV2Flow) && useV2Flow)
|
||||
{
|
||||
Trace.Info($"Service enforced useV2Flow: {useV2Flow}");
|
||||
runnerSettings.UseV2Flow = useV2Flow;
|
||||
}
|
||||
|
||||
_term.WriteSection("Runner settings");
|
||||
|
||||
// We will Combine() what's stored with root. Defaults to string a relative path
|
||||
@@ -505,7 +536,7 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
if (string.IsNullOrEmpty(settings.GitHubUrl))
|
||||
{
|
||||
var credProvider = GetCredentialProvider(command, settings.ServerUrl);
|
||||
creds = credProvider.GetVssCredentials(HostContext);
|
||||
creds = credProvider.GetVssCredentials(HostContext, allowAuthUrlV2: false);
|
||||
Trace.Info("legacy vss cred retrieved");
|
||||
}
|
||||
else
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
public interface ICredentialManager : IRunnerService
|
||||
{
|
||||
ICredentialProvider GetCredentialProvider(string credType);
|
||||
VssCredentials LoadCredentials();
|
||||
VssCredentials LoadCredentials(bool allowAuthUrlV2);
|
||||
}
|
||||
|
||||
public class CredentialManager : RunnerService, ICredentialManager
|
||||
@@ -40,7 +40,7 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
return creds;
|
||||
}
|
||||
|
||||
public VssCredentials LoadCredentials()
|
||||
public VssCredentials LoadCredentials(bool allowAuthUrlV2)
|
||||
{
|
||||
IConfigurationStore store = HostContext.GetService<IConfigurationStore>();
|
||||
|
||||
@@ -51,21 +51,16 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
|
||||
CredentialData credData = store.GetCredentials();
|
||||
var migratedCred = store.GetMigratedCredentials();
|
||||
if (migratedCred != null)
|
||||
if (migratedCred != null &&
|
||||
migratedCred.Scheme == Constants.Configuration.OAuth)
|
||||
{
|
||||
credData = migratedCred;
|
||||
|
||||
// Re-write .credentials with Token URL
|
||||
store.SaveCredential(credData);
|
||||
|
||||
// Delete .credentials_migrated
|
||||
store.DeleteMigratedCredential();
|
||||
}
|
||||
|
||||
ICredentialProvider credProv = GetCredentialProvider(credData.Scheme);
|
||||
credProv.CredentialData = credData;
|
||||
|
||||
VssCredentials creds = credProv.GetVssCredentials(HostContext);
|
||||
VssCredentials creds = credProv.GetVssCredentials(HostContext, allowAuthUrlV2);
|
||||
|
||||
return creds;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.OAuth;
|
||||
|
||||
namespace GitHub.Runner.Listener.Configuration
|
||||
@@ -10,7 +10,7 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
{
|
||||
Boolean RequireInteractive { get; }
|
||||
CredentialData CredentialData { get; set; }
|
||||
VssCredentials GetVssCredentials(IHostContext context);
|
||||
VssCredentials GetVssCredentials(IHostContext context, bool allowAuthUrlV2);
|
||||
void EnsureCredential(IHostContext context, CommandSettings command, string serverUrl);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
public virtual Boolean RequireInteractive => false;
|
||||
public CredentialData CredentialData { get; set; }
|
||||
|
||||
public abstract VssCredentials GetVssCredentials(IHostContext context);
|
||||
public abstract VssCredentials GetVssCredentials(IHostContext context, bool allowAuthUrlV2);
|
||||
public abstract void EnsureCredential(IHostContext context, CommandSettings command, string serverUrl);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
{
|
||||
public OAuthAccessTokenCredential() : base(Constants.Configuration.OAuthAccessToken) { }
|
||||
|
||||
public override VssCredentials GetVssCredentials(IHostContext context)
|
||||
public override VssCredentials GetVssCredentials(IHostContext context, bool allowAuthUrlV2)
|
||||
{
|
||||
ArgUtil.NotNull(context, nameof(context));
|
||||
Tracing trace = context.GetTrace(nameof(OAuthAccessTokenCredential));
|
||||
|
||||
@@ -22,10 +22,18 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
// Nothing to verify here
|
||||
}
|
||||
|
||||
public override VssCredentials GetVssCredentials(IHostContext context)
|
||||
public override VssCredentials GetVssCredentials(IHostContext context, bool allowAuthUrlV2)
|
||||
{
|
||||
var clientId = this.CredentialData.Data.GetValueOrDefault("clientId", null);
|
||||
var authorizationUrl = this.CredentialData.Data.GetValueOrDefault("authorizationUrl", null);
|
||||
var authorizationUrlV2 = this.CredentialData.Data.GetValueOrDefault("authorizationUrlV2", null);
|
||||
|
||||
if (allowAuthUrlV2 &&
|
||||
!string.IsNullOrEmpty(authorizationUrlV2) &&
|
||||
context.AllowAuthMigration)
|
||||
{
|
||||
authorizationUrl = authorizationUrlV2;
|
||||
}
|
||||
|
||||
// For back compat with .credential file that doesn't has 'oauthEndpointUrl' section
|
||||
var oauthEndpointUrl = this.CredentialData.Data.GetValueOrDefault("oauthEndpointUrl", authorizationUrl);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#if OS_WINDOWS
|
||||
#pragma warning disable CA1416
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
@@ -84,4 +85,5 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore CA1416
|
||||
#endif
|
||||
|
||||
@@ -545,6 +545,8 @@ namespace GitHub.Runner.Listener
|
||||
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.");
|
||||
|
||||
try
|
||||
{
|
||||
var jobServer = await InitializeJobServerAsync(systemConnection);
|
||||
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = detailInfo };
|
||||
unhandledExceptionIssue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.WorkerCrash;
|
||||
@@ -569,6 +571,12 @@ namespace GitHub.Runner.Listener
|
||||
throw new NotSupportedException($"JobServer type '{jobServer.GetType().Name}' is not supported.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error($"Catch exception during log worker process unhandled exception.");
|
||||
Trace.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
TaskResult result = TaskResultUtil.TranslateFromReturnCode(returnCode);
|
||||
Trace.Info($"finish job request for job {message.JobId} with result: {result}");
|
||||
@@ -1198,7 +1206,7 @@ namespace GitHub.Runner.Listener
|
||||
jobAnnotations.Add(annotation.Value);
|
||||
}
|
||||
|
||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, TaskResult.Failed, outputs: null, stepResults: null, jobAnnotations: jobAnnotations, environmentUrl: null, CancellationToken.None);
|
||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, TaskResult.Failed, outputs: null, stepResults: null, jobAnnotations: jobAnnotations, environmentUrl: null, telemetry: null, billingOwnerId: message.BillingOwnerId, CancellationToken.None);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace GitHub.Runner.Listener
|
||||
Task<TaskAgentMessage> GetNextMessageAsync(CancellationToken token);
|
||||
Task DeleteMessageAsync(TaskAgentMessage message);
|
||||
|
||||
Task RefreshListenerTokenAsync(CancellationToken token);
|
||||
Task RefreshListenerTokenAsync();
|
||||
void OnJobStatus(object sender, JobStatusEventArgs e);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace GitHub.Runner.Listener
|
||||
private ITerminal _term;
|
||||
private IRunnerServer _runnerServer;
|
||||
private IBrokerServer _brokerServer;
|
||||
private ICredentialManager _credMgr;
|
||||
private TaskAgentSession _session;
|
||||
private TimeSpan _getNextMessageRetryInterval;
|
||||
private bool _accessTokenRevoked = false;
|
||||
@@ -54,8 +55,9 @@ namespace GitHub.Runner.Listener
|
||||
private TaskAgentStatus runnerStatus = TaskAgentStatus.Online;
|
||||
private CancellationTokenSource _getMessagesTokenSource;
|
||||
private VssCredentials _creds;
|
||||
|
||||
private bool _isBrokerSession = false;
|
||||
private VssCredentials _credsV2;
|
||||
private bool _needRefreshCredsV2 = false;
|
||||
private bool _handlerInitialized = false;
|
||||
|
||||
public override void Initialize(IHostContext hostContext)
|
||||
{
|
||||
@@ -64,6 +66,7 @@ namespace GitHub.Runner.Listener
|
||||
_term = HostContext.GetService<ITerminal>();
|
||||
_runnerServer = HostContext.GetService<IRunnerServer>();
|
||||
_brokerServer = hostContext.GetService<IBrokerServer>();
|
||||
_credMgr = hostContext.GetService<ICredentialManager>();
|
||||
}
|
||||
|
||||
public async Task<CreateSessionResult> CreateSessionAsync(CancellationToken token)
|
||||
@@ -78,8 +81,7 @@ namespace GitHub.Runner.Listener
|
||||
|
||||
// Create connection.
|
||||
Trace.Info("Loading Credentials");
|
||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
||||
_creds = credMgr.LoadCredentials();
|
||||
_creds = _credMgr.LoadCredentials(allowAuthUrlV2: false);
|
||||
|
||||
var agent = new TaskAgentReference
|
||||
{
|
||||
@@ -113,16 +115,6 @@ namespace GitHub.Runner.Listener
|
||||
_settings.PoolId,
|
||||
taskAgentSession,
|
||||
token);
|
||||
|
||||
if (_session.BrokerMigrationMessage != null)
|
||||
{
|
||||
Trace.Info("Runner session is in migration mode: Creating Broker session with BrokerBaseUrl: {0}", _session.BrokerMigrationMessage.BrokerBaseUrl);
|
||||
|
||||
await _brokerServer.UpdateConnectionIfNeeded(_session.BrokerMigrationMessage.BrokerBaseUrl, _creds);
|
||||
_session = await _brokerServer.CreateSessionAsync(taskAgentSession, token);
|
||||
_isBrokerSession = true;
|
||||
}
|
||||
|
||||
Trace.Info($"Session created.");
|
||||
if (encounteringError)
|
||||
{
|
||||
@@ -131,6 +123,13 @@ namespace GitHub.Runner.Listener
|
||||
encounteringError = false;
|
||||
}
|
||||
|
||||
if (!_handlerInitialized)
|
||||
{
|
||||
Trace.Info("Registering AuthMigrationChanged event handler.");
|
||||
HostContext.AuthMigrationChanged += HandleAuthMigrationChanged;
|
||||
_handlerInitialized = true;
|
||||
}
|
||||
|
||||
return CreateSessionResult.Success;
|
||||
}
|
||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||
@@ -196,16 +195,16 @@ namespace GitHub.Runner.Listener
|
||||
{
|
||||
if (_session != null && _session.SessionId != Guid.Empty)
|
||||
{
|
||||
if (_handlerInitialized)
|
||||
{
|
||||
HostContext.AuthMigrationChanged -= HandleAuthMigrationChanged;
|
||||
}
|
||||
|
||||
if (!_accessTokenRevoked)
|
||||
{
|
||||
using (var ts = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
|
||||
{
|
||||
await _runnerServer.DeleteAgentSessionAsync(_settings.PoolId, _session.SessionId, ts.Token);
|
||||
|
||||
if (_isBrokerSession)
|
||||
{
|
||||
await _brokerServer.DeleteSessionAsync(ts.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -261,14 +260,19 @@ namespace GitHub.Runner.Listener
|
||||
// Decrypt the message body if the session is using encryption
|
||||
message = DecryptMessage(message);
|
||||
|
||||
|
||||
if (message != null && message.MessageType == BrokerMigrationMessage.MessageType)
|
||||
{
|
||||
Trace.Info("BrokerMigration message received. Polling Broker for messages...");
|
||||
|
||||
var migrationMessage = JsonUtility.FromString<BrokerMigrationMessage>(message.Body);
|
||||
|
||||
await _brokerServer.UpdateConnectionIfNeeded(migrationMessage.BrokerBaseUrl, _creds);
|
||||
_credsV2 = _credMgr.LoadCredentials(allowAuthUrlV2: true);
|
||||
await _brokerServer.UpdateConnectionIfNeeded(migrationMessage.BrokerBaseUrl, _credsV2);
|
||||
if (_needRefreshCredsV2)
|
||||
{
|
||||
Trace.Info("Refreshing credentials for V2.");
|
||||
await _brokerServer.ForceRefreshConnection(_credsV2);
|
||||
_needRefreshCredsV2 = false;
|
||||
}
|
||||
|
||||
message = await _brokerServer.GetRunnerMessageAsync(_session.SessionId,
|
||||
runnerStatus,
|
||||
BuildConstants.RunnerPackage.Version,
|
||||
@@ -306,7 +310,16 @@ namespace GitHub.Runner.Listener
|
||||
_accessTokenRevoked = true;
|
||||
throw;
|
||||
}
|
||||
catch (AccessDeniedException e) when (e.ErrorCode == 1)
|
||||
catch (HostedRunnerDeprovisionedException)
|
||||
{
|
||||
Trace.Info("Hosted runner has been deprovisioned.");
|
||||
throw;
|
||||
}
|
||||
catch (AccessDeniedException e) when (e.ErrorCode == 1 && !HostContext.AllowAuthMigration)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (RunnerNotFoundException) when (!HostContext.AllowAuthMigration)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
@@ -315,12 +328,19 @@ namespace GitHub.Runner.Listener
|
||||
Trace.Error("Catch exception during get next message.");
|
||||
Trace.Error(ex);
|
||||
|
||||
// clear out potential message for broker migration,
|
||||
// in case the exception is thrown from get message from broker-listener.
|
||||
message = null;
|
||||
|
||||
// don't retry if SkipSessionRecover = true, DT service will delete agent session to stop agent from taking more jobs.
|
||||
if (ex is TaskAgentSessionExpiredException && !_settings.SkipSessionRecover && (await CreateSessionAsync(token) == CreateSessionResult.Success))
|
||||
if (!HostContext.AllowAuthMigration &&
|
||||
ex is TaskAgentSessionExpiredException &&
|
||||
!_settings.SkipSessionRecover && (await CreateSessionAsync(token) == CreateSessionResult.Success))
|
||||
{
|
||||
Trace.Info($"{nameof(TaskAgentSessionExpiredException)} received, recovered by recreate session.");
|
||||
}
|
||||
else if (!IsGetNextMessageExceptionRetriable(ex))
|
||||
else if (!HostContext.AllowAuthMigration &&
|
||||
!IsGetNextMessageExceptionRetriable(ex))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
@@ -347,6 +367,12 @@ namespace GitHub.Runner.Listener
|
||||
encounteringError = true;
|
||||
}
|
||||
|
||||
if (HostContext.AllowAuthMigration)
|
||||
{
|
||||
Trace.Info("Disable migration mode for 60 minutes.");
|
||||
HostContext.DeferAuthMigration(TimeSpan.FromMinutes(60), $"Get next message failed with exception: {ex}");
|
||||
}
|
||||
|
||||
// re-create VssConnection before next retry
|
||||
await _runnerServer.RefreshConnectionAsync(RunnerConnectionType.MessageQueue, TimeSpan.FromSeconds(60));
|
||||
|
||||
@@ -404,10 +430,11 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RefreshListenerTokenAsync(CancellationToken cancellationToken)
|
||||
public async Task RefreshListenerTokenAsync()
|
||||
{
|
||||
await _runnerServer.RefreshConnectionAsync(RunnerConnectionType.MessageQueue, TimeSpan.FromSeconds(60));
|
||||
await _brokerServer.ForceRefreshConnection(_creds);
|
||||
_credsV2 = _credMgr.LoadCredentials(allowAuthUrlV2: true);
|
||||
await _brokerServer.ForceRefreshConnection(_credsV2);
|
||||
}
|
||||
|
||||
private TaskAgentMessage DecryptMessage(TaskAgentMessage message)
|
||||
@@ -459,6 +486,7 @@ namespace GitHub.Runner.Listener
|
||||
ex is TaskAgentPoolNotFoundException ||
|
||||
ex is TaskAgentSessionExpiredException ||
|
||||
ex is AccessDeniedException ||
|
||||
ex is RunnerNotFoundException ||
|
||||
ex is VssUnauthorizedException)
|
||||
{
|
||||
Trace.Info($"Non-retriable exception: {ex.Message}");
|
||||
@@ -525,7 +553,8 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
else if (ex is TaskAgentPoolNotFoundException ||
|
||||
ex is AccessDeniedException ||
|
||||
ex is VssUnauthorizedException)
|
||||
ex is VssUnauthorizedException ||
|
||||
(ex is VssOAuthTokenRequestException oauthEx && oauthEx.Error != "server_error"))
|
||||
{
|
||||
Trace.Info($"Non-retriable exception: {ex.Message}");
|
||||
return false;
|
||||
@@ -536,5 +565,11 @@ namespace GitHub.Runner.Listener
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleAuthMigrationChanged(object sender, EventArgs e)
|
||||
{
|
||||
Trace.Info($"Auth migration changed. Current allow auth migration state: {HostContext.AllowAuthMigration}");
|
||||
_needRefreshCredsV2 = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Services.WebApi;
|
||||
|
||||
namespace GitHub.Runner.Listener
|
||||
{
|
||||
@@ -144,6 +145,12 @@ namespace GitHub.Runner.Listener
|
||||
trace.Error(e);
|
||||
return Constants.Runner.ReturnCode.TerminatedError;
|
||||
}
|
||||
catch (RunnerNotFoundException e)
|
||||
{
|
||||
terminal.WriteError($"An error occurred: {e.Message}");
|
||||
trace.Error(e);
|
||||
return Constants.Runner.ReturnCode.TerminatedError;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
terminal.WriteError($"An error occurred: {e.Message}");
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64;win-arm64</RuntimeIdentifiers>
|
||||
<SelfContained>true</SelfContained>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<NoWarn>NU1701;NU1603</NoWarn>
|
||||
<NoWarn>NU1701;NU1603;SYSLIB0050;SYSLIB0051</NoWarn>
|
||||
<Version>$(Version)</Version>
|
||||
<PredefinedCulturesOnly>false</PredefinedCulturesOnly>
|
||||
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
|
||||
@@ -18,11 +19,11 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="4.4.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="4.4.0" />
|
||||
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -14,7 +16,9 @@ using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Listener.Check;
|
||||
using GitHub.Runner.Listener.Configuration;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Services.OAuth;
|
||||
using GitHub.Services.WebApi;
|
||||
using GitHub.Services.WebApi.Jwt;
|
||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||
|
||||
namespace GitHub.Runner.Listener
|
||||
@@ -31,6 +35,14 @@ namespace GitHub.Runner.Listener
|
||||
private ITerminal _term;
|
||||
private bool _inConfigStage;
|
||||
private ManualResetEvent _completedCommand = new(false);
|
||||
private readonly ConcurrentQueue<string> _authMigrationTelemetries = new();
|
||||
private Task _authMigrationTelemetryTask;
|
||||
private readonly object _authMigrationTelemetryLock = new();
|
||||
private Task _authMigrationClaimsCheckTask;
|
||||
private readonly object _authMigrationClaimsCheckLock = new();
|
||||
private IRunnerServer _runnerServer;
|
||||
private CancellationTokenSource _authMigrationTelemetryTokenSource = new();
|
||||
private CancellationTokenSource _authMigrationClaimsCheckTokenSource = new();
|
||||
|
||||
// <summary>
|
||||
// Helps avoid excessive calls to Run Service when encountering non-retriable errors from /acquirejob.
|
||||
@@ -51,6 +63,7 @@ namespace GitHub.Runner.Listener
|
||||
base.Initialize(hostContext);
|
||||
_term = HostContext.GetService<ITerminal>();
|
||||
_acquireJobThrottler = HostContext.CreateService<IErrorThrottler>();
|
||||
_runnerServer = HostContext.GetService<IRunnerServer>();
|
||||
}
|
||||
|
||||
public async Task<int> ExecuteCommand(CommandSettings command)
|
||||
@@ -66,6 +79,8 @@ namespace GitHub.Runner.Listener
|
||||
//register a SIGTERM handler
|
||||
HostContext.Unloading += Runner_Unloading;
|
||||
|
||||
HostContext.AuthMigrationChanged += HandleAuthMigrationChanged;
|
||||
|
||||
// TODO Unit test to cover this logic
|
||||
Trace.Info(nameof(ExecuteCommand));
|
||||
var configManager = HostContext.GetService<IConfigurationManager>();
|
||||
@@ -228,10 +243,12 @@ namespace GitHub.Runner.Listener
|
||||
var configFile = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), config.Key);
|
||||
var configContent = Convert.FromBase64String(config.Value);
|
||||
#if OS_WINDOWS
|
||||
#pragma warning disable CA1416
|
||||
if (configFile == HostContext.GetConfigFile(WellKnownConfigFile.RSACredentials))
|
||||
{
|
||||
configContent = ProtectedData.Protect(configContent, null, DataProtectionScope.LocalMachine);
|
||||
}
|
||||
#pragma warning restore CA1416
|
||||
#endif
|
||||
File.WriteAllBytes(configFile, configContent);
|
||||
File.SetAttributes(configFile, File.GetAttributes(configFile) | FileAttributes.Hidden);
|
||||
@@ -298,8 +315,17 @@ namespace GitHub.Runner.Listener
|
||||
_term.WriteLine("https://docs.github.com/en/actions/hosting-your-own-runners/autoscaling-with-self-hosted-runners#using-ephemeral-runners-for-autoscaling", ConsoleColor.Yellow);
|
||||
}
|
||||
|
||||
var cred = store.GetCredentials();
|
||||
if (cred != null &&
|
||||
cred.Scheme == Constants.Configuration.OAuth &&
|
||||
cred.Data.ContainsKey("EnableAuthMigrationByDefault"))
|
||||
{
|
||||
Trace.Info("Enable auth migration by default.");
|
||||
HostContext.EnableAuthMigration("EnableAuthMigrationByDefault");
|
||||
}
|
||||
|
||||
// Run the runner interactively or as service
|
||||
return await RunAsync(settings, command.RunOnce || settings.Ephemeral);
|
||||
return await ExecuteRunnerAsync(settings, command.RunOnce || settings.Ephemeral);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -309,6 +335,9 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
finally
|
||||
{
|
||||
_authMigrationClaimsCheckTokenSource?.Cancel();
|
||||
_authMigrationTelemetryTokenSource?.Cancel();
|
||||
HostContext.AuthMigrationChanged -= HandleAuthMigrationChanged;
|
||||
_term.CancelKeyPress -= CtrlCHandler;
|
||||
HostContext.Unloading -= Runner_Unloading;
|
||||
_completedCommand.Set();
|
||||
@@ -358,12 +387,12 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
}
|
||||
|
||||
private IMessageListener GetMesageListener(RunnerSettings settings)
|
||||
private IMessageListener GetMessageListener(RunnerSettings settings, bool isMigratedSettings = false)
|
||||
{
|
||||
if (settings.UseV2Flow)
|
||||
{
|
||||
Trace.Info($"Using BrokerMessageListener");
|
||||
var brokerListener = new BrokerMessageListener();
|
||||
var brokerListener = new BrokerMessageListener(settings, isMigratedSettings);
|
||||
brokerListener.Initialize(HostContext);
|
||||
return brokerListener;
|
||||
}
|
||||
@@ -377,7 +406,56 @@ namespace GitHub.Runner.Listener
|
||||
try
|
||||
{
|
||||
Trace.Info(nameof(RunAsync));
|
||||
_listener = GetMesageListener(settings);
|
||||
|
||||
// First try using migrated settings if available
|
||||
var configManager = HostContext.GetService<IConfigurationManager>();
|
||||
RunnerSettings migratedSettings = null;
|
||||
|
||||
try
|
||||
{
|
||||
migratedSettings = configManager.LoadMigratedSettings();
|
||||
Trace.Info("Loaded migrated settings from .runner_migrated file");
|
||||
Trace.Info(migratedSettings);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// If migrated settings file doesn't exist or can't be loaded, we'll use the provided settings
|
||||
Trace.Info($"Failed to load migrated settings: {ex.Message}");
|
||||
}
|
||||
|
||||
bool usedMigratedSettings = false;
|
||||
|
||||
if (migratedSettings != null)
|
||||
{
|
||||
// Try to create session with migrated settings first
|
||||
Trace.Info("Attempting to create session using migrated settings");
|
||||
_listener = GetMessageListener(migratedSettings, isMigratedSettings: true);
|
||||
|
||||
try
|
||||
{
|
||||
CreateSessionResult createSessionResult = await _listener.CreateSessionAsync(HostContext.RunnerShutdownToken);
|
||||
if (createSessionResult == CreateSessionResult.Success)
|
||||
{
|
||||
Trace.Info("Successfully created session with migrated settings");
|
||||
settings = migratedSettings; // Use migrated settings for the rest of the process
|
||||
usedMigratedSettings = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace.Warning($"Failed to create session with migrated settings: {createSessionResult}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error($"Exception when creating session with migrated settings: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
// If migrated settings weren't used or session creation failed, use original settings
|
||||
if (!usedMigratedSettings)
|
||||
{
|
||||
Trace.Info("Falling back to original .runner settings");
|
||||
_listener = GetMessageListener(settings);
|
||||
CreateSessionResult createSessionResult = await _listener.CreateSessionAsync(HostContext.RunnerShutdownToken);
|
||||
if (createSessionResult == CreateSessionResult.SessionConflict)
|
||||
{
|
||||
@@ -387,6 +465,7 @@ namespace GitHub.Runner.Listener
|
||||
{
|
||||
return Constants.Runner.ReturnCode.TerminatedError;
|
||||
}
|
||||
}
|
||||
|
||||
HostContext.WritePerfCounter("SessionCreated");
|
||||
|
||||
@@ -399,6 +478,8 @@ namespace GitHub.Runner.Listener
|
||||
// Should we try to cleanup ephemeral runners
|
||||
bool runOnceJobCompleted = false;
|
||||
bool skipSessionDeletion = false;
|
||||
bool restartSession = false; // Flag to indicate session restart
|
||||
bool restartSessionPending = false;
|
||||
try
|
||||
{
|
||||
var notification = HostContext.GetService<IJobNotification>();
|
||||
@@ -414,6 +495,15 @@ namespace GitHub.Runner.Listener
|
||||
|
||||
while (!HostContext.RunnerShutdownToken.IsCancellationRequested)
|
||||
{
|
||||
// Check if we need to restart the session and can do so (job dispatcher not busy)
|
||||
if (restartSessionPending && !jobDispatcher.Busy)
|
||||
{
|
||||
Trace.Info("Pending session restart detected and job dispatcher is not busy. Restarting session now.");
|
||||
messageQueueLoopTokenSource.Cancel();
|
||||
restartSession = true;
|
||||
break;
|
||||
}
|
||||
|
||||
TaskAgentMessage message = null;
|
||||
bool skipMessageDeletion = false;
|
||||
try
|
||||
@@ -568,21 +658,21 @@ namespace GitHub.Runner.Listener
|
||||
|
||||
// Create connection
|
||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
||||
var creds = credMgr.LoadCredentials();
|
||||
|
||||
if (string.IsNullOrEmpty(messageRef.RunServiceUrl))
|
||||
{
|
||||
var creds = credMgr.LoadCredentials(allowAuthUrlV2: false);
|
||||
var actionsRunServer = HostContext.CreateService<IActionsRunServer>();
|
||||
await actionsRunServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
||||
jobRequestMessage = await actionsRunServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
||||
}
|
||||
else
|
||||
{
|
||||
var credsV2 = credMgr.LoadCredentials(allowAuthUrlV2: true);
|
||||
var runServer = HostContext.CreateService<IRunServer>();
|
||||
await runServer.ConnectAsync(new Uri(messageRef.RunServiceUrl), creds);
|
||||
await runServer.ConnectAsync(new Uri(messageRef.RunServiceUrl), credsV2);
|
||||
try
|
||||
{
|
||||
jobRequestMessage = await runServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
||||
jobRequestMessage = await runServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageRef.BillingOwnerId, messageQueueLoopTokenSource.Token);
|
||||
_acquireJobThrottler.Reset();
|
||||
}
|
||||
catch (Exception ex) when (
|
||||
@@ -597,6 +687,13 @@ namespace GitHub.Runner.Listener
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error($"Caught exception from acquiring job message: {ex}");
|
||||
|
||||
if (HostContext.AllowAuthMigration)
|
||||
{
|
||||
Trace.Info("Disable migration mode for 60 minutes.");
|
||||
HostContext.DeferAuthMigration(TimeSpan.FromMinutes(60), $"Acquire job failed with exception: {ex}");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -631,7 +728,29 @@ namespace GitHub.Runner.Listener
|
||||
else if (string.Equals(message.MessageType, TaskAgentMessageTypes.ForceTokenRefresh))
|
||||
{
|
||||
Trace.Info("Received ForceTokenRefreshMessage");
|
||||
await _listener.RefreshListenerTokenAsync(messageQueueLoopTokenSource.Token);
|
||||
await _listener.RefreshListenerTokenAsync();
|
||||
}
|
||||
else if (string.Equals(message.MessageType, RunnerRefreshConfigMessage.MessageType))
|
||||
{
|
||||
var runnerRefreshConfigMessage = JsonUtility.FromString<RunnerRefreshConfigMessage>(message.Body);
|
||||
Trace.Info($"Received RunnerRefreshConfigMessage for '{runnerRefreshConfigMessage.ConfigType}' config file");
|
||||
var configUpdater = HostContext.GetService<IRunnerConfigUpdater>();
|
||||
await configUpdater.UpdateRunnerConfigAsync(
|
||||
runnerQualifiedId: runnerRefreshConfigMessage.RunnerQualifiedId,
|
||||
configType: runnerRefreshConfigMessage.ConfigType,
|
||||
serviceType: runnerRefreshConfigMessage.ServiceType,
|
||||
configRefreshUrl: runnerRefreshConfigMessage.ConfigRefreshUrl);
|
||||
|
||||
// Set flag to schedule session restart if ConfigType is "runner"
|
||||
if (string.Equals(runnerRefreshConfigMessage.ConfigType, "runner", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Trace.Info("Runner configuration was updated. Session restart has been scheduled");
|
||||
restartSessionPending = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace.Info($"No session restart needed for config type: {runnerRefreshConfigMessage.ConfigType}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -686,19 +805,243 @@ namespace GitHub.Runner.Listener
|
||||
|
||||
if (settings.Ephemeral && runOnceJobCompleted)
|
||||
{
|
||||
var configManager = HostContext.GetService<IConfigurationManager>();
|
||||
configManager.DeleteLocalRunnerConfig();
|
||||
}
|
||||
}
|
||||
|
||||
// After cleanup, check if we need to restart the session
|
||||
if (restartSession)
|
||||
{
|
||||
Trace.Info("Restarting runner session after config update...");
|
||||
return Constants.Runner.ReturnCode.RunnerConfigurationRefreshed;
|
||||
}
|
||||
}
|
||||
catch (TaskAgentAccessTokenExpiredException)
|
||||
{
|
||||
Trace.Info("Runner OAuth token has been revoked. Shutting down.");
|
||||
}
|
||||
catch (HostedRunnerDeprovisionedException)
|
||||
{
|
||||
Trace.Info("Hosted runner has been deprovisioned. Shutting down.");
|
||||
}
|
||||
|
||||
return Constants.Runner.ReturnCode.Success;
|
||||
}
|
||||
|
||||
private async Task<int> ExecuteRunnerAsync(RunnerSettings settings, bool runOnce)
|
||||
{
|
||||
int returnCode = Constants.Runner.ReturnCode.Success;
|
||||
bool restart = false;
|
||||
do
|
||||
{
|
||||
restart = false;
|
||||
returnCode = await RunAsync(settings, runOnce);
|
||||
|
||||
if (returnCode == Constants.Runner.ReturnCode.RunnerConfigurationRefreshed)
|
||||
{
|
||||
Trace.Info("Runner configuration was refreshed, restarting session...");
|
||||
// Reload settings in case they changed
|
||||
var configManager = HostContext.GetService<IConfigurationManager>();
|
||||
settings = configManager.LoadSettings();
|
||||
restart = true;
|
||||
}
|
||||
} while (restart);
|
||||
|
||||
return returnCode;
|
||||
}
|
||||
|
||||
private void HandleAuthMigrationChanged(object sender, AuthMigrationEventArgs e)
|
||||
{
|
||||
Trace.Verbose("Handle AuthMigrationChanged in Runner");
|
||||
_authMigrationTelemetries.Enqueue($"{DateTime.UtcNow.ToString("O")}: {e.Trace}");
|
||||
|
||||
// only start the telemetry reporting task once auth migration is changed (enabled or disabled)
|
||||
lock (_authMigrationTelemetryLock)
|
||||
{
|
||||
if (_authMigrationTelemetryTask == null)
|
||||
{
|
||||
_authMigrationTelemetryTask = ReportAuthMigrationTelemetryAsync(_authMigrationTelemetryTokenSource.Token);
|
||||
}
|
||||
}
|
||||
|
||||
// only start the claims check task once auth migration is changed (enabled or disabled)
|
||||
lock (_authMigrationClaimsCheckLock)
|
||||
{
|
||||
if (_authMigrationClaimsCheckTask == null)
|
||||
{
|
||||
_authMigrationClaimsCheckTask = CheckOAuthTokenClaimsAsync(_authMigrationClaimsCheckTokenSource.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckOAuthTokenClaimsAsync(CancellationToken token)
|
||||
{
|
||||
string[] expectedClaims =
|
||||
[
|
||||
"owner_id",
|
||||
"runner_id",
|
||||
"runner_group_id",
|
||||
"scale_set_id",
|
||||
"is_ephemeral",
|
||||
"labels"
|
||||
];
|
||||
|
||||
try
|
||||
{
|
||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await HostContext.Delay(TimeSpan.FromMinutes(100), token);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// Ignore cancellation
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (!HostContext.AllowAuthMigration)
|
||||
{
|
||||
Trace.Info("Skip checking oauth token claims since auth migration is disabled.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var baselineCred = credMgr.LoadCredentials(allowAuthUrlV2: false);
|
||||
var authV2Cred = credMgr.LoadCredentials(allowAuthUrlV2: true);
|
||||
|
||||
if (!(baselineCred.Federated is VssOAuthCredential baselineVssOAuthCred) ||
|
||||
!(authV2Cred.Federated is VssOAuthCredential vssOAuthCredV2) ||
|
||||
baselineVssOAuthCred == null ||
|
||||
vssOAuthCredV2 == null)
|
||||
{
|
||||
Trace.Info("Skip checking oauth token claims for non-oauth credentials");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.Equals(baselineVssOAuthCred.AuthorizationUrl.AbsoluteUri, vssOAuthCredV2.AuthorizationUrl.AbsoluteUri, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Trace.Info("Skip checking oauth token claims for same authorization url");
|
||||
continue;
|
||||
}
|
||||
|
||||
var baselineProvider = baselineVssOAuthCred.GetTokenProvider(baselineVssOAuthCred.AuthorizationUrl);
|
||||
var v2Provider = vssOAuthCredV2.GetTokenProvider(vssOAuthCredV2.AuthorizationUrl);
|
||||
try
|
||||
{
|
||||
using (var timeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
|
||||
using (var requestTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token, timeoutTokenSource.Token))
|
||||
{
|
||||
var baselineToken = await baselineProvider.GetTokenAsync(null, requestTokenSource.Token);
|
||||
var v2Token = await v2Provider.GetTokenAsync(null, requestTokenSource.Token);
|
||||
if (baselineToken is VssOAuthAccessToken baselineAccessToken &&
|
||||
v2Token is VssOAuthAccessToken v2AccessToken &&
|
||||
!string.IsNullOrEmpty(baselineAccessToken.Value) &&
|
||||
!string.IsNullOrEmpty(v2AccessToken.Value))
|
||||
{
|
||||
var baselineJwt = JsonWebToken.Create(baselineAccessToken.Value);
|
||||
var baselineClaims = baselineJwt.ExtractClaims();
|
||||
var v2Jwt = JsonWebToken.Create(v2AccessToken.Value);
|
||||
var v2Claims = v2Jwt.ExtractClaims();
|
||||
|
||||
// Log extracted claims for debugging
|
||||
Trace.Verbose($"Baseline token expected claims: {string.Join(", ", baselineClaims
|
||||
.Where(c => expectedClaims.Contains(c.Type.ToLowerInvariant()))
|
||||
.Select(c => $"{c.Type}:{c.Value}"))}");
|
||||
Trace.Verbose($"V2 token expected claims: {string.Join(", ", v2Claims
|
||||
.Where(c => expectedClaims.Contains(c.Type.ToLowerInvariant()))
|
||||
.Select(c => $"{c.Type}:{c.Value}"))}");
|
||||
|
||||
foreach (var claim in expectedClaims)
|
||||
{
|
||||
// if baseline has the claim, v2 should have it too with exactly same value.
|
||||
if (baselineClaims.FirstOrDefault(c => c.Type.ToLowerInvariant() == claim) is Claim baselineClaim &&
|
||||
!string.IsNullOrEmpty(baselineClaim?.Value))
|
||||
{
|
||||
var v2Claim = v2Claims.FirstOrDefault(c => c.Type.ToLowerInvariant() == claim);
|
||||
if (v2Claim?.Value != baselineClaim.Value)
|
||||
{
|
||||
Trace.Info($"Token Claim mismatch between two issuers. Expected: {baselineClaim.Type}:{baselineClaim.Value}. Actual: {v2Claim?.Type ?? "Empty"}:{v2Claim?.Value ?? "Empty"}");
|
||||
HostContext.DeferAuthMigration(TimeSpan.FromMinutes(60), $"Expected claim {baselineClaim.Type}:{baselineClaim.Value} does not match {v2Claim?.Type ?? "Empty"}:{v2Claim?.Value ?? "Empty"}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Trace.Info("OAuth token claims check passed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error("Failed to fetch and check OAuth token claims.");
|
||||
Trace.Error(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error("Failed to check OAuth token claims in background.");
|
||||
Trace.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReportAuthMigrationTelemetryAsync(CancellationToken token)
|
||||
{
|
||||
var configManager = HostContext.GetService<IConfigurationManager>();
|
||||
var runnerSettings = configManager.LoadSettings();
|
||||
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await HostContext.Delay(TimeSpan.FromSeconds(60), token);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// Ignore cancellation
|
||||
}
|
||||
|
||||
Trace.Verbose("Checking for auth migration telemetry to report");
|
||||
while (_authMigrationTelemetries.TryDequeue(out var telemetry))
|
||||
{
|
||||
Trace.Verbose($"Reporting auth migration telemetry: {telemetry}");
|
||||
if (runnerSettings != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
|
||||
{
|
||||
await _runnerServer.UpdateAgentUpdateStateAsync(runnerSettings.PoolId, runnerSettings.AgentId, "RefreshConfig", telemetry, tokenSource.Token);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error("Failed to report auth migration telemetry.");
|
||||
Trace.Error(ex);
|
||||
_authMigrationTelemetries.Enqueue(telemetry);
|
||||
}
|
||||
}
|
||||
|
||||
if (!token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await HostContext.Delay(TimeSpan.FromSeconds(10), token);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// Ignore cancellation
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PrintUsage(CommandSettings command)
|
||||
{
|
||||
string separator;
|
||||
|
||||
287
src/Runner.Listener/RunnerConfigUpdater.cs
Normal file
287
src/Runner.Listener/RunnerConfigUpdater.cs
Normal file
@@ -0,0 +1,287 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Services.Common;
|
||||
|
||||
namespace GitHub.Runner.Listener
|
||||
{
|
||||
[ServiceLocator(Default = typeof(RunnerConfigUpdater))]
|
||||
public interface IRunnerConfigUpdater : IRunnerService
|
||||
{
|
||||
Task UpdateRunnerConfigAsync(string runnerQualifiedId, string configType, string serviceType, string configRefreshUrl);
|
||||
}
|
||||
|
||||
public sealed class RunnerConfigUpdater : RunnerService, IRunnerConfigUpdater
|
||||
{
|
||||
private RunnerSettings _settings;
|
||||
private CredentialData _credData;
|
||||
private IRunnerServer _runnerServer;
|
||||
private IConfigurationStore _store;
|
||||
|
||||
public override void Initialize(IHostContext hostContext)
|
||||
{
|
||||
base.Initialize(hostContext);
|
||||
_store = hostContext.GetService<IConfigurationStore>();
|
||||
_settings = _store.GetSettings();
|
||||
_credData = _store.GetCredentials();
|
||||
_runnerServer = HostContext.GetService<IRunnerServer>();
|
||||
}
|
||||
|
||||
public async Task UpdateRunnerConfigAsync(string runnerQualifiedId, string configType, string serviceType, string configRefreshUrl)
|
||||
{
|
||||
Trace.Entering();
|
||||
try
|
||||
{
|
||||
ArgUtil.NotNullOrEmpty(runnerQualifiedId, nameof(runnerQualifiedId));
|
||||
ArgUtil.NotNullOrEmpty(configType, nameof(configType));
|
||||
ArgUtil.NotNullOrEmpty(serviceType, nameof(serviceType));
|
||||
ArgUtil.NotNullOrEmpty(configRefreshUrl, nameof(configRefreshUrl));
|
||||
|
||||
// make sure the runner qualified id matches the current runner
|
||||
if (!await VerifyRunnerQualifiedId(runnerQualifiedId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// keep the timeout short to avoid blocking the main thread
|
||||
using (var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
|
||||
{
|
||||
switch (configType.ToLowerInvariant())
|
||||
{
|
||||
case "runner":
|
||||
await UpdateRunnerSettingsAsync(serviceType, configRefreshUrl, tokenSource.Token);
|
||||
break;
|
||||
case "credentials":
|
||||
await UpdateRunnerCredentialsAsync(serviceType, configRefreshUrl, tokenSource.Token);
|
||||
break;
|
||||
default:
|
||||
Trace.Error($"Invalid config type '{configType}'.");
|
||||
await ReportTelemetryAsync($"Invalid config type '{configType}'.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error($"Failed to update runner '{configType}' config.");
|
||||
Trace.Error(ex);
|
||||
await ReportTelemetryAsync($"Failed to update runner '{configType}' config: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateRunnerSettingsAsync(string serviceType, string configRefreshUrl, CancellationToken token)
|
||||
{
|
||||
Trace.Entering();
|
||||
// read the current runner settings and encode with base64
|
||||
var runnerConfig = HostContext.GetConfigFile(WellKnownConfigFile.Runner);
|
||||
string runnerConfigContent = File.ReadAllText(runnerConfig, Encoding.UTF8);
|
||||
var encodedConfig = Convert.ToBase64String(Encoding.UTF8.GetBytes(runnerConfigContent));
|
||||
if (string.IsNullOrEmpty(encodedConfig))
|
||||
{
|
||||
await ReportTelemetryAsync("Failed to get encoded runner settings.");
|
||||
return;
|
||||
}
|
||||
|
||||
// exchange the encoded runner settings with the service
|
||||
string refreshedEncodedConfig = await RefreshRunnerConfigAsync(encodedConfig, serviceType, "runner", configRefreshUrl, token);
|
||||
if (string.IsNullOrEmpty(refreshedEncodedConfig))
|
||||
{
|
||||
// service will return empty string if there is no change in the config
|
||||
return;
|
||||
}
|
||||
|
||||
var decodedConfig = Encoding.UTF8.GetString(Convert.FromBase64String(refreshedEncodedConfig));
|
||||
RunnerSettings refreshedRunnerConfig;
|
||||
try
|
||||
{
|
||||
refreshedRunnerConfig = StringUtil.ConvertFromJson<RunnerSettings>(decodedConfig);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error($"Failed to convert runner config from json '{decodedConfig}'.");
|
||||
Trace.Error(ex);
|
||||
await ReportTelemetryAsync($"Failed to convert runner config '{decodedConfig}' from json: {ex}");
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure the runner id and name in the refreshed config match the current runner
|
||||
if (refreshedRunnerConfig?.AgentId != _settings.AgentId)
|
||||
{
|
||||
Trace.Error($"Runner id in refreshed config '{refreshedRunnerConfig?.AgentId.ToString() ?? "Empty"}' does not match the current runner '{_settings.AgentId}'.");
|
||||
await ReportTelemetryAsync($"Runner id in refreshed config '{refreshedRunnerConfig?.AgentId.ToString() ?? "Empty"}' does not match the current runner '{_settings.AgentId}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (refreshedRunnerConfig?.AgentName != _settings.AgentName)
|
||||
{
|
||||
Trace.Error($"Runner name in refreshed config '{refreshedRunnerConfig?.AgentName ?? "Empty"}' does not match the current runner '{_settings.AgentName}'.");
|
||||
await ReportTelemetryAsync($"Runner name in refreshed config '{refreshedRunnerConfig?.AgentName ?? "Empty"}' does not match the current runner '{_settings.AgentName}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
// save the refreshed runner settings as a separate file
|
||||
_store.SaveMigratedSettings(refreshedRunnerConfig);
|
||||
await ReportTelemetryAsync("Runner settings updated successfully.");
|
||||
}
|
||||
|
||||
private async Task UpdateRunnerCredentialsAsync(string serviceType, string configRefreshUrl, CancellationToken token)
|
||||
{
|
||||
Trace.Entering();
|
||||
// read the current runner credentials and encode with base64
|
||||
var credConfig = HostContext.GetConfigFile(WellKnownConfigFile.Credentials);
|
||||
string credConfigContent = File.ReadAllText(credConfig, Encoding.UTF8);
|
||||
var encodedConfig = Convert.ToBase64String(Encoding.UTF8.GetBytes(credConfigContent));
|
||||
if (string.IsNullOrEmpty(encodedConfig))
|
||||
{
|
||||
await ReportTelemetryAsync("Failed to get encoded credentials.");
|
||||
return;
|
||||
}
|
||||
|
||||
CredentialData currentCred = _store.GetCredentials();
|
||||
if (currentCred == null)
|
||||
{
|
||||
await ReportTelemetryAsync("Failed to get current credentials.");
|
||||
return;
|
||||
}
|
||||
|
||||
// we only support refreshing OAuth credentials which is used by self-hosted runners.
|
||||
if (currentCred.Scheme != Constants.Configuration.OAuth)
|
||||
{
|
||||
await ReportTelemetryAsync($"Not supported credential scheme '{currentCred.Scheme}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
// exchange the encoded runner credentials with the service
|
||||
string refreshedEncodedConfig = await RefreshRunnerConfigAsync(encodedConfig, serviceType, "credentials", configRefreshUrl, token);
|
||||
if (string.IsNullOrEmpty(refreshedEncodedConfig))
|
||||
{
|
||||
// service will return empty string if there is no change in the config
|
||||
return;
|
||||
}
|
||||
|
||||
var decodedConfig = Encoding.UTF8.GetString(Convert.FromBase64String(refreshedEncodedConfig));
|
||||
CredentialData refreshedCredConfig;
|
||||
try
|
||||
{
|
||||
refreshedCredConfig = StringUtil.ConvertFromJson<CredentialData>(decodedConfig);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error($"Failed to convert credentials config from json '{decodedConfig}'.");
|
||||
Trace.Error(ex);
|
||||
await ReportTelemetryAsync($"Failed to convert credentials config '{decodedConfig}' from json: {ex}");
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure the credential scheme in the refreshed config match the current credential scheme
|
||||
if (refreshedCredConfig?.Scheme != _credData.Scheme)
|
||||
{
|
||||
Trace.Error($"Credential scheme in refreshed config '{refreshedCredConfig?.Scheme ?? "Empty"}' does not match the current credential scheme '{_credData.Scheme}'.");
|
||||
await ReportTelemetryAsync($"Credential scheme in refreshed config '{refreshedCredConfig?.Scheme ?? "Empty"}' does not match the current credential scheme '{_credData.Scheme}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_credData.Scheme == Constants.Configuration.OAuth)
|
||||
{
|
||||
// make sure the credential clientId in the refreshed config match the current credential clientId for OAuth auth scheme
|
||||
var clientId = _credData.Data.GetValueOrDefault("clientId", null);
|
||||
var refreshedClientId = refreshedCredConfig.Data.GetValueOrDefault("clientId", null);
|
||||
if (clientId != refreshedClientId)
|
||||
{
|
||||
Trace.Error($"Credential clientId in refreshed config '{refreshedClientId ?? "Empty"}' does not match the current credential clientId '{clientId}'.");
|
||||
await ReportTelemetryAsync($"Credential clientId in refreshed config '{refreshedClientId ?? "Empty"}' does not match the current credential clientId '{clientId}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure the credential authorizationUrl in the refreshed config match the current credential authorizationUrl for OAuth auth scheme
|
||||
var authorizationUrl = _credData.Data.GetValueOrDefault("authorizationUrl", null);
|
||||
var refreshedAuthorizationUrl = refreshedCredConfig.Data.GetValueOrDefault("authorizationUrl", null);
|
||||
if (authorizationUrl != refreshedAuthorizationUrl)
|
||||
{
|
||||
Trace.Error($"Credential authorizationUrl in refreshed config '{refreshedAuthorizationUrl ?? "Empty"}' does not match the current credential authorizationUrl '{authorizationUrl}'.");
|
||||
await ReportTelemetryAsync($"Credential authorizationUrl in refreshed config '{refreshedAuthorizationUrl ?? "Empty"}' does not match the current credential authorizationUrl '{authorizationUrl}'.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// save the refreshed runner credentials as a separate file
|
||||
_store.SaveMigratedCredential(refreshedCredConfig);
|
||||
|
||||
if (refreshedCredConfig.Data.ContainsKey("authorizationUrlV2"))
|
||||
{
|
||||
HostContext.EnableAuthMigration("Credential file updated");
|
||||
await ReportTelemetryAsync("Runner credentials updated successfully. Auth migration is enabled.");
|
||||
}
|
||||
else
|
||||
{
|
||||
HostContext.DeferAuthMigration(TimeSpan.FromDays(365), "Credential file does not contain authorizationUrlV2");
|
||||
await ReportTelemetryAsync("Runner credentials updated successfully. Auth migration is disabled.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> VerifyRunnerQualifiedId(string runnerQualifiedId)
|
||||
{
|
||||
Trace.Entering();
|
||||
Trace.Info($"Verifying runner qualified id: {runnerQualifiedId}");
|
||||
var idParts = runnerQualifiedId.Split("/", StringSplitOptions.RemoveEmptyEntries);
|
||||
if (idParts.Length != 4 || idParts[3] != _settings.AgentId.ToString())
|
||||
{
|
||||
Trace.Error($"Runner qualified id '{runnerQualifiedId}' does not match the current runner '{_settings.AgentId}'.");
|
||||
await ReportTelemetryAsync($"Runner qualified id '{runnerQualifiedId}' does not match the current runner '{_settings.AgentId}'.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<string> RefreshRunnerConfigAsync(string encodedConfig, string serviceType, string configType, string configRefreshUrl, CancellationToken token)
|
||||
{
|
||||
string refreshedEncodedConfig;
|
||||
switch (serviceType.ToLowerInvariant())
|
||||
{
|
||||
case "pipelines":
|
||||
try
|
||||
{
|
||||
refreshedEncodedConfig = await _runnerServer.RefreshRunnerConfigAsync((int)_settings.AgentId, configType, encodedConfig, token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error($"Failed to refresh runner {configType} config with service.");
|
||||
Trace.Error(ex);
|
||||
await ReportTelemetryAsync($"Failed to refresh {configType} config: {ex}");
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case "runner-admin":
|
||||
throw new NotSupportedException("Runner admin service is not supported.");
|
||||
default:
|
||||
Trace.Error($"Invalid service type '{serviceType}'.");
|
||||
await ReportTelemetryAsync($"Invalid service type '{serviceType}'.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return refreshedEncodedConfig;
|
||||
}
|
||||
|
||||
private async Task ReportTelemetryAsync(string telemetry)
|
||||
{
|
||||
Trace.Entering();
|
||||
try
|
||||
{
|
||||
using (var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
|
||||
{
|
||||
await _runnerServer.UpdateAgentUpdateStateAsync(_settings.PoolId, _settings.AgentId, "RefreshConfig", telemetry, tokenSource.Token);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error("Failed to report telemetry.");
|
||||
Trace.Error(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,14 @@ namespace GitHub.Runner.Listener
|
||||
{
|
||||
[DataMember(Name = "id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[DataMember(Name = "runner_request_id")]
|
||||
public string RunnerRequestId { get; set; }
|
||||
|
||||
[DataMember(Name = "run_service_url")]
|
||||
public string RunServiceUrl { get; set; }
|
||||
|
||||
[DataMember(Name = "billing_owner_id")]
|
||||
public string BillingOwnerId { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64;win-arm64</RuntimeIdentifiers>
|
||||
<SelfContained>true</SelfContained>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<NoWarn>NU1701;NU1603</NoWarn>
|
||||
<NoWarn>NU1701;NU1603;SYSLIB0050;SYSLIB0051</NoWarn>
|
||||
<Version>$(Version)</Version>
|
||||
<PredefinedCulturesOnly>false</PredefinedCulturesOnly>
|
||||
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<OutputType>Library</OutputType>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64;win-arm64</RuntimeIdentifiers>
|
||||
<SelfContained>true</SelfContained>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<NoWarn>NU1701;NU1603</NoWarn>
|
||||
<NoWarn>NU1701;NU1603;SYSLIB0050;SYSLIB0051</NoWarn>
|
||||
<Version>$(Version)</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<OutputType>Library</OutputType>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64;win-arm64</RuntimeIdentifiers>
|
||||
<SelfContained>true</SelfContained>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<NoWarn>NU1701;NU1603</NoWarn>
|
||||
<NoWarn>NU1701;NU1603;SYSLIB0050;SYSLIB0051</NoWarn>
|
||||
<Version>$(Version)</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -14,9 +15,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.4.0" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="4.4.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
|
||||
@@ -60,5 +60,15 @@ namespace GitHub.Runner.Sdk
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static string GetVssRequestId(HttpResponseHeaders headers)
|
||||
{
|
||||
if (headers != null &&
|
||||
headers.TryGetValues("x-vss-e2eid", out var headerValues))
|
||||
{
|
||||
return headerValues.FirstOrDefault();
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -775,8 +775,20 @@ namespace GitHub.Runner.Worker
|
||||
// make sure we get a clean folder ready to use.
|
||||
IOUtil.DeleteDirectory(destDirectory, executionContext.CancellationToken);
|
||||
Directory.CreateDirectory(destDirectory);
|
||||
|
||||
if (downloadInfo.PackageDetails != null)
|
||||
{
|
||||
executionContext.Output($"##[group]Download immutable action package '{downloadInfo.NameWithOwner}@{downloadInfo.Ref}'");
|
||||
executionContext.Output($"Version: {downloadInfo.PackageDetails.Version}");
|
||||
executionContext.Output($"Digest: {downloadInfo.PackageDetails.ManifestDigest}");
|
||||
executionContext.Output($"Source commit SHA: {downloadInfo.ResolvedSha}");
|
||||
executionContext.Output("##[endgroup]");
|
||||
}
|
||||
else
|
||||
{
|
||||
executionContext.Output($"Download action repository '{downloadInfo.NameWithOwner}@{downloadInfo.Ref}' (SHA:{downloadInfo.ResolvedSha})");
|
||||
}
|
||||
}
|
||||
|
||||
//download and extract action in a temp folder and rename it on success
|
||||
string tempDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), "_temp_" + Guid.NewGuid());
|
||||
|
||||
@@ -508,6 +508,9 @@ namespace GitHub.Runner.Worker
|
||||
Status = _record.State,
|
||||
Number = _record.Order,
|
||||
Name = _record.Name,
|
||||
ActionName = StepTelemetry?.Action,
|
||||
Ref = StepTelemetry?.Ref,
|
||||
Type = StepTelemetry?.Type,
|
||||
StartedAt = _record.StartTime,
|
||||
CompletedAt = _record.FinishTime,
|
||||
Annotations = new List<Annotation>()
|
||||
@@ -812,11 +815,6 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
Global.Variables = new Variables(HostContext, variables);
|
||||
|
||||
if (Global.Variables.GetBoolean("DistributedTask.ForceInternalNodeVersionOnRunnerTo16") ?? false)
|
||||
{
|
||||
Environment.SetEnvironmentVariable(Constants.Variables.Agent.ForcedInternalNodeVersion, "node16");
|
||||
}
|
||||
|
||||
// Environment variables shared across all actions
|
||||
Global.EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer);
|
||||
|
||||
@@ -864,7 +862,21 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
ExpressionValues["secrets"] = Global.Variables.ToSecretsContext();
|
||||
ExpressionValues["runner"] = new RunnerContext();
|
||||
ExpressionValues["job"] = new JobContext();
|
||||
|
||||
Trace.Info("Initializing Job context");
|
||||
var jobContext = new JobContext();
|
||||
if (Global.Variables.GetBoolean(Constants.Runner.Features.AddCheckRunIdToJobContext) ?? false)
|
||||
{
|
||||
ExpressionValues.TryGetValue("job", out var jobDictionary);
|
||||
if (jobDictionary != null)
|
||||
{
|
||||
foreach (var pair in jobDictionary.AssertDictionary("job"))
|
||||
{
|
||||
jobContext[pair.Key] = pair.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
ExpressionValues["job"] = jobContext;
|
||||
|
||||
Trace.Info("Initialize GitHub context");
|
||||
var githubAccessToken = new StringContextData(Global.Variables.Get("system.github.token"));
|
||||
|
||||
@@ -57,72 +57,13 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
handler = HostContext.CreateService<INodeScriptActionHandler>();
|
||||
var nodeData = data as NodeJSActionExecutionData;
|
||||
|
||||
// With node12 EoL in 04/2022, we want to be able to uniformly upgrade all JS actions to node16 from the server
|
||||
if (string.Equals(nodeData.NodeVersion, "node12", StringComparison.InvariantCultureIgnoreCase))
|
||||
// With node12 EoL in 04/2022 and node16 EoL in 09/23, we want to execute all JS actions using node20
|
||||
if (string.Equals(nodeData.NodeVersion, "node12", StringComparison.InvariantCultureIgnoreCase) ||
|
||||
string.Equals(nodeData.NodeVersion, "node16", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var repoAction = action as Pipelines.RepositoryPathReference;
|
||||
if (repoAction != null)
|
||||
{
|
||||
var warningActions = new HashSet<string>();
|
||||
if (executionContext.Global.Variables.TryGetValue(Constants.Runner.EnforcedNode12DetectedAfterEndOfLifeEnvVariable, out var node16ForceWarnings))
|
||||
{
|
||||
warningActions = StringUtil.ConvertFromJson<HashSet<string>>(node16ForceWarnings);
|
||||
}
|
||||
|
||||
string repoActionFullName;
|
||||
if (string.IsNullOrEmpty(repoAction.Name))
|
||||
{
|
||||
repoActionFullName = repoAction.Path; // local actions don't have a 'Name'
|
||||
}
|
||||
else
|
||||
{
|
||||
repoActionFullName = $"{repoAction.Name}/{repoAction.Path ?? string.Empty}".TrimEnd('/') + $"@{repoAction.Ref}";
|
||||
}
|
||||
|
||||
warningActions.Add(repoActionFullName);
|
||||
executionContext.Global.Variables.Set("Node16ForceActionsWarnings", StringUtil.ConvertToJson(warningActions));
|
||||
}
|
||||
nodeData.NodeVersion = "node16";
|
||||
}
|
||||
|
||||
var localForceActionsToNode20 = StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable(Constants.Variables.Agent.ManualForceActionsToNode20));
|
||||
executionContext.Global.EnvironmentVariables.TryGetValue(Constants.Variables.Actions.ManualForceActionsToNode20, out var workflowForceActionsToNode20);
|
||||
var enforceNode20Locally = !string.IsNullOrWhiteSpace(workflowForceActionsToNode20) ? StringUtil.ConvertToBoolean(workflowForceActionsToNode20) : localForceActionsToNode20;
|
||||
if (string.Equals(nodeData.NodeVersion, "node16")
|
||||
&& ((executionContext.Global.Variables.GetBoolean("DistributedTask.ForceGithubJavascriptActionsToNode20") ?? false) || enforceNode20Locally))
|
||||
{
|
||||
executionContext.Global.EnvironmentVariables.TryGetValue(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion, out var workflowOptOut);
|
||||
var isWorkflowOptOutSet = !string.IsNullOrWhiteSpace(workflowOptOut);
|
||||
var isLocalOptOut = StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion));
|
||||
bool isOptOut = isWorkflowOptOutSet ? StringUtil.ConvertToBoolean(workflowOptOut) : isLocalOptOut;
|
||||
|
||||
if (!isOptOut)
|
||||
{
|
||||
var repoAction = action as Pipelines.RepositoryPathReference;
|
||||
if (repoAction != null)
|
||||
{
|
||||
var warningActions = new HashSet<string>();
|
||||
if (executionContext.Global.Variables.TryGetValue(Constants.Runner.EnforcedNode16DetectedAfterEndOfLifeEnvVariable, out var node20ForceWarnings))
|
||||
{
|
||||
warningActions = StringUtil.ConvertFromJson<HashSet<string>>(node20ForceWarnings);
|
||||
}
|
||||
|
||||
string repoActionFullName;
|
||||
if (string.IsNullOrEmpty(repoAction.Name))
|
||||
{
|
||||
repoActionFullName = repoAction.Path; // local actions don't have a 'Name'
|
||||
}
|
||||
else
|
||||
{
|
||||
repoActionFullName = $"{repoAction.Name}/{repoAction.Path ?? string.Empty}".TrimEnd('/') + $"@{repoAction.Ref}";
|
||||
}
|
||||
|
||||
warningActions.Add(repoActionFullName);
|
||||
executionContext.Global.Variables.Set(Constants.Runner.EnforcedNode16DetectedAfterEndOfLifeEnvVariable, StringUtil.ConvertToJson(warningActions));
|
||||
}
|
||||
nodeData.NodeVersion = "node20";
|
||||
}
|
||||
}
|
||||
|
||||
(handler as INodeScriptActionHandler).Data = nodeData;
|
||||
}
|
||||
else if (data.ExecutionType == ActionExecutionType.Script)
|
||||
|
||||
@@ -72,6 +72,11 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
Environment["ACTIONS_RESULTS_URL"] = resultsUrl;
|
||||
}
|
||||
|
||||
if (ExecutionContext.Global.Variables.GetBoolean("actions_uses_cache_service_v2") ?? false)
|
||||
{
|
||||
Environment["ACTIONS_CACHE_SERVICE_V2"] = bool.TrueString;
|
||||
}
|
||||
|
||||
// Resolve the target script.
|
||||
string target = null;
|
||||
if (stage == ActionRunStage.Main)
|
||||
@@ -105,23 +110,6 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
workingDirectory = HostContext.GetDirectory(WellKnownDirectory.Work);
|
||||
}
|
||||
|
||||
if (string.Equals(Data.NodeVersion, "node12", StringComparison.OrdinalIgnoreCase) &&
|
||||
Constants.Runner.PlatformArchitecture.Equals(Constants.Architecture.Arm64))
|
||||
{
|
||||
ExecutionContext.Output($"The node12 is not supported. Use node16 instead.");
|
||||
Data.NodeVersion = "node16";
|
||||
}
|
||||
|
||||
string forcedNodeVersion = System.Environment.GetEnvironmentVariable(Constants.Variables.Agent.ForcedActionsNodeVersion);
|
||||
if (forcedNodeVersion == "node16" && Data.NodeVersion != "node16")
|
||||
{
|
||||
Data.NodeVersion = "node16";
|
||||
}
|
||||
|
||||
if (forcedNodeVersion == "node20" && Data.NodeVersion != "node20")
|
||||
{
|
||||
Data.NodeVersion = "node20";
|
||||
}
|
||||
var nodeRuntimeVersion = await StepHost.DetermineNodeRuntimeVersion(ExecutionContext, Data.NodeVersion);
|
||||
ExecutionContext.StepTelemetry.Type = nodeRuntimeVersion;
|
||||
string file = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), nodeRuntimeVersion, "bin", $"node{IOUtil.ExeExtension}");
|
||||
@@ -143,28 +131,6 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
// Remove environment variable that may cause conflicts with the node within the runner.
|
||||
Environment.Remove("NODE_ICU_DATA"); // https://github.com/actions/runner/issues/795
|
||||
|
||||
if (string.Equals(Data.NodeVersion, Constants.Runner.DeprecatedNodeVersion, StringComparison.OrdinalIgnoreCase) && (ExecutionContext.Global.Variables.GetBoolean(Constants.Runner.Features.Node16Warning) ?? false))
|
||||
{
|
||||
var repoAction = Action as RepositoryPathReference;
|
||||
var warningActions = new HashSet<string>();
|
||||
if (ExecutionContext.Global.Variables.TryGetValue(Constants.Runner.DeprecatedNodeDetectedAfterEndOfLifeActions, out var deprecatedNodeWarnings))
|
||||
{
|
||||
warningActions = StringUtil.ConvertFromJson<HashSet<string>>(deprecatedNodeWarnings);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(repoAction.Name))
|
||||
{
|
||||
// local actions don't have a 'Name'
|
||||
warningActions.Add(repoAction.Path);
|
||||
}
|
||||
else
|
||||
{
|
||||
warningActions.Add($"{repoAction.Name}/{repoAction.Path ?? string.Empty}".TrimEnd('/') + $"@{repoAction.Ref}");
|
||||
}
|
||||
|
||||
ExecutionContext.Global.Variables.Set(Constants.Runner.DeprecatedNodeDetectedAfterEndOfLifeActions, StringUtil.ConvertToJson(warningActions));
|
||||
}
|
||||
|
||||
using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager))
|
||||
using (var stderrManager = new OutputManager(ExecutionContext, ActionCommandManager))
|
||||
{
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace GitHub.Runner.Worker
|
||||
public sealed class IssueMatcher
|
||||
{
|
||||
private string _defaultSeverity;
|
||||
private string _defaultFromPath;
|
||||
private string _owner;
|
||||
private IssuePattern[] _patterns;
|
||||
private IssueMatch[] _state;
|
||||
@@ -29,6 +30,7 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
_owner = config.Owner;
|
||||
_defaultSeverity = config.Severity;
|
||||
_defaultFromPath = config.FromPath;
|
||||
_patterns = config.Patterns.Select(x => new IssuePattern(x, timeout)).ToArray();
|
||||
Reset();
|
||||
}
|
||||
@@ -59,6 +61,19 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
|
||||
public string DefaultFromPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_defaultFromPath == null)
|
||||
{
|
||||
_defaultFromPath = string.Empty;
|
||||
}
|
||||
|
||||
return _defaultFromPath;
|
||||
}
|
||||
}
|
||||
|
||||
public IssueMatch Match(string line)
|
||||
{
|
||||
// Single pattern
|
||||
@@ -69,7 +84,7 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
if (regexMatch.Success)
|
||||
{
|
||||
return new IssueMatch(null, pattern, regexMatch.Groups, DefaultSeverity);
|
||||
return new IssueMatch(null, pattern, regexMatch.Groups, DefaultSeverity, DefaultFromPath);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -110,7 +125,7 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
|
||||
// Return
|
||||
return new IssueMatch(runningMatch, pattern, regexMatch.Groups, DefaultSeverity);
|
||||
return new IssueMatch(runningMatch, pattern, regexMatch.Groups, DefaultSeverity, DefaultFromPath);
|
||||
}
|
||||
// Not the last pattern
|
||||
else
|
||||
@@ -184,7 +199,7 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
public sealed class IssueMatch
|
||||
{
|
||||
public IssueMatch(IssueMatch runningMatch, IssuePattern pattern, GroupCollection groups, string defaultSeverity = null)
|
||||
public IssueMatch(IssueMatch runningMatch, IssuePattern pattern, GroupCollection groups, string defaultSeverity = null, string defaultFromPath = null)
|
||||
{
|
||||
File = runningMatch?.File ?? GetValue(groups, pattern.File);
|
||||
Line = runningMatch?.Line ?? GetValue(groups, pattern.Line);
|
||||
@@ -198,6 +213,11 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
Severity = defaultSeverity;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(FromPath) && !string.IsNullOrEmpty(defaultFromPath))
|
||||
{
|
||||
FromPath = defaultFromPath;
|
||||
}
|
||||
}
|
||||
|
||||
public string File { get; }
|
||||
@@ -282,6 +302,9 @@ namespace GitHub.Runner.Worker
|
||||
[DataMember(Name = "pattern")]
|
||||
private IssuePatternConfig[] _patterns;
|
||||
|
||||
[DataMember(Name = "fromPath")]
|
||||
private string _fromPath;
|
||||
|
||||
public string Owner
|
||||
{
|
||||
get
|
||||
@@ -318,6 +341,24 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
|
||||
public string FromPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_fromPath == null)
|
||||
{
|
||||
_fromPath = string.Empty;
|
||||
}
|
||||
|
||||
return _fromPath;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_fromPath = value;
|
||||
}
|
||||
}
|
||||
|
||||
public IssuePatternConfig[] Patterns
|
||||
{
|
||||
get
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Common;
|
||||
|
||||
@@ -56,5 +56,31 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double? CheckRunId
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.TryGetValue("check_run_id", out var value) && value is NumberContextData number)
|
||||
{
|
||||
return number.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value.HasValue)
|
||||
{
|
||||
this["check_run_id"] = new NumberContextData(value.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
this["check_run_id"] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Services.Common;
|
||||
using Newtonsoft.Json;
|
||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||
|
||||
namespace GitHub.Runner.Worker
|
||||
@@ -42,11 +43,13 @@ namespace GitHub.Runner.Worker
|
||||
public sealed class JobExtension : RunnerService, IJobExtension
|
||||
{
|
||||
private readonly HashSet<string> _existingProcesses = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly List<Task<string>> _connectivityCheckTasks = new();
|
||||
private readonly List<Task<CheckResult>> _connectivityCheckTasks = new();
|
||||
private bool _processCleanup;
|
||||
private string _processLookupId = $"github_{Guid.NewGuid()}";
|
||||
private CancellationTokenSource _diskSpaceCheckToken = new();
|
||||
private Task _diskSpaceCheckTask = null;
|
||||
private CancellationTokenSource _serviceConnectivityCheckToken = new();
|
||||
private Task _serviceConnectivityCheckTask = null;
|
||||
|
||||
// Download all required actions.
|
||||
// Make sure all condition inputs are valid.
|
||||
@@ -127,10 +130,6 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
|
||||
// Check OS warning
|
||||
var osWarningChecker = HostContext.GetService<IOSWarningChecker>();
|
||||
await osWarningChecker.CheckOSAsync(context);
|
||||
|
||||
try
|
||||
{
|
||||
var tokenPermissions = jobContext.Global.Variables.Get("system.github.token.permissions") ?? "";
|
||||
@@ -458,11 +457,14 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
foreach (var checkUrl in checkUrls)
|
||||
{
|
||||
_connectivityCheckTasks.Add(CheckConnectivity(checkUrl));
|
||||
_connectivityCheckTasks.Add(CheckConnectivity(checkUrl, accessToken: string.Empty, timeoutInSeconds: 5, token: CancellationToken.None));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Trace.Info($"Start checking service connectivity in background.");
|
||||
_serviceConnectivityCheckTask = CheckServiceConnectivityAsync(context, _serviceConnectivityCheckToken.Token);
|
||||
|
||||
return steps;
|
||||
}
|
||||
catch (OperationCanceledException ex) when (jobContext.CancellationToken.IsCancellationRequested)
|
||||
@@ -696,7 +698,7 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
var result = await check;
|
||||
Trace.Info($"Connectivity check result: {result}");
|
||||
context.Global.JobTelemetry.Add(new JobTelemetry() { Type = JobTelemetryType.ConnectivityCheck, Message = result });
|
||||
context.Global.JobTelemetry.Add(new JobTelemetry() { Type = JobTelemetryType.ConnectivityCheck, Message = $"{result.EndpointUrl}: {result.StatusCode}" });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -706,6 +708,22 @@ namespace GitHub.Runner.Worker
|
||||
context.Global.JobTelemetry.Add(new JobTelemetry() { Type = JobTelemetryType.ConnectivityCheck, Message = $"Fail to check server connectivity. {ex.Message}" });
|
||||
}
|
||||
}
|
||||
|
||||
// Collect service connectivity check result
|
||||
if (_serviceConnectivityCheckTask != null)
|
||||
{
|
||||
_serviceConnectivityCheckToken.Cancel();
|
||||
try
|
||||
{
|
||||
await _serviceConnectivityCheckTask;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error($"Fail to check service connectivity.");
|
||||
Trace.Error(ex);
|
||||
context.Global.JobTelemetry.Add(new JobTelemetry() { Type = JobTelemetryType.ConnectivityCheck, Message = $"Fail to check service connectivity. {ex.Message}" });
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -721,11 +739,13 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> CheckConnectivity(string endpointUrl)
|
||||
private async Task<CheckResult> CheckConnectivity(string endpointUrl, string accessToken, int timeoutInSeconds, CancellationToken token)
|
||||
{
|
||||
Trace.Info($"Check server connectivity for {endpointUrl}.");
|
||||
string result = string.Empty;
|
||||
using (var timeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
|
||||
CheckResult result = new CheckResult() { EndpointUrl = endpointUrl };
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
using (var timeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutInSeconds)))
|
||||
using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token, timeoutTokenSource.Token))
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -733,21 +753,44 @@ namespace GitHub.Runner.Worker
|
||||
using (var httpClient = new HttpClient(httpClientHandler))
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
||||
var response = await httpClient.GetAsync(endpointUrl, timeoutTokenSource.Token);
|
||||
result = $"{endpointUrl}: {response.StatusCode}";
|
||||
if (!string.IsNullOrEmpty(accessToken))
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {accessToken}");
|
||||
}
|
||||
|
||||
var response = await httpClient.GetAsync(endpointUrl, linkedTokenSource.Token);
|
||||
result.StatusCode = $"{response.StatusCode}";
|
||||
|
||||
var githubRequestId = UrlUtil.GetGitHubRequestId(response.Headers);
|
||||
var vssRequestId = UrlUtil.GetVssRequestId(response.Headers);
|
||||
if (!string.IsNullOrEmpty(githubRequestId))
|
||||
{
|
||||
result.RequestId = githubRequestId;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(vssRequestId))
|
||||
{
|
||||
result.RequestId = vssRequestId;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is OperationCanceledException && token.IsCancellationRequested)
|
||||
{
|
||||
Trace.Error($"Request canceled during connectivity check: {ex}");
|
||||
result.StatusCode = "canceled";
|
||||
}
|
||||
catch (Exception ex) when (ex is OperationCanceledException && timeoutTokenSource.IsCancellationRequested)
|
||||
{
|
||||
Trace.Error($"Request timeout during connectivity check: {ex}");
|
||||
result = $"{endpointUrl}: timeout";
|
||||
result.StatusCode = "timeout";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error($"Catch exception during connectivity check: {ex}");
|
||||
result = $"{endpointUrl}: {ex.Message}";
|
||||
result.StatusCode = $"{ex.Message}";
|
||||
}
|
||||
}
|
||||
stopwatch.Stop();
|
||||
result.DurationInMs = (int)stopwatch.ElapsedMilliseconds;
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -785,6 +828,84 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckServiceConnectivityAsync(IExecutionContext context, CancellationToken token)
|
||||
{
|
||||
var connectionTest = context.Global.Variables.Get(WellKnownDistributedTaskVariables.RunnerServiceConnectivityTest);
|
||||
if (string.IsNullOrEmpty(connectionTest))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ServiceConnectivityCheckInput checkConnectivityInfo;
|
||||
try
|
||||
{
|
||||
checkConnectivityInfo = StringUtil.ConvertFromJson<ServiceConnectivityCheckInput>(connectionTest);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.Global.JobTelemetry.Add(new JobTelemetry() { Type = JobTelemetryType.General, Message = $"Fail to parse JSON. {ex.Message}" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkConnectivityInfo == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure interval is at least 10 seconds
|
||||
checkConnectivityInfo.IntervalInSecond = Math.Max(10, checkConnectivityInfo.IntervalInSecond);
|
||||
|
||||
var systemConnection = context.Global.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
||||
var accessToken = systemConnection.Authorization.Parameters[EndpointAuthorizationParameters.AccessToken];
|
||||
|
||||
var testResult = new ServiceConnectivityCheckResult();
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
foreach (var endpoint in checkConnectivityInfo.Endpoints)
|
||||
{
|
||||
if (string.IsNullOrEmpty(endpoint.Key) || string.IsNullOrEmpty(endpoint.Value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!testResult.EndpointsResult.ContainsKey(endpoint.Key))
|
||||
{
|
||||
testResult.EndpointsResult[endpoint.Key] = new List<string>();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await CheckConnectivity(endpoint.Value, accessToken: accessToken, timeoutInSeconds: checkConnectivityInfo.RequestTimeoutInSecond, token);
|
||||
testResult.EndpointsResult[endpoint.Key].Add($"{result.StartTime:s}: {result.StatusCode} - {result.RequestId} - {result.DurationInMs}ms");
|
||||
if (!testResult.HasFailure &&
|
||||
result.StatusCode != "OK" &&
|
||||
result.StatusCode != "canceled")
|
||||
{
|
||||
// track if any endpoint is not reachable
|
||||
testResult.HasFailure = true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
testResult.EndpointsResult[endpoint.Key].Add($"{DateTime.UtcNow:s}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(checkConnectivityInfo.IntervalInSecond), token);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
var telemetryData = StringUtil.ConvertToJson(testResult, Formatting.None);
|
||||
Trace.Verbose($"Connectivity check result: {telemetryData}");
|
||||
context.Global.JobTelemetry.Add(new JobTelemetry() { Type = JobTelemetryType.ConnectivityCheck, Message = telemetryData });
|
||||
}
|
||||
|
||||
private Dictionary<int, Process> SnapshotProcesses()
|
||||
{
|
||||
Dictionary<int, Process> snapshot = new();
|
||||
@@ -816,5 +937,23 @@ namespace GitHub.Runner.Worker
|
||||
throw new ArgumentException("Jobs without a job container are forbidden on this runner, please add a 'container:' to your job or contact your self-hosted runner administrator.");
|
||||
}
|
||||
}
|
||||
|
||||
private class CheckResult
|
||||
{
|
||||
public CheckResult()
|
||||
{
|
||||
StartTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public string EndpointUrl { get; set; }
|
||||
|
||||
public DateTime StartTime { get; set; }
|
||||
|
||||
public string StatusCode { get; set; }
|
||||
|
||||
public string RequestId { get; set; }
|
||||
|
||||
public int DurationInMs { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.WebApi;
|
||||
using Sdk.RSWebApi.Contracts;
|
||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||
|
||||
namespace GitHub.Runner.Worker
|
||||
@@ -49,7 +50,8 @@ namespace GitHub.Runner.Worker
|
||||
if (message.Variables.TryGetValue(Constants.Variables.System.OrchestrationId, out VariableValue orchestrationId) &&
|
||||
!string.IsNullOrEmpty(orchestrationId.Value))
|
||||
{
|
||||
HostContext.UserAgents.Add(new ProductInfoHeaderValue("OrchestrationId", orchestrationId.Value));
|
||||
// make the orchestration id the first item in the user-agent header to avoid get truncated in server log.
|
||||
HostContext.UserAgents.Insert(0, new ProductInfoHeaderValue("OrchestrationId", orchestrationId.Value));
|
||||
|
||||
// make sure orchestration id is in the user-agent header.
|
||||
VssUtil.InitializeVssClientSettings(HostContext.UserAgents, HostContext.WebProxy);
|
||||
@@ -278,26 +280,14 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
jobContext.Debug($"Finishing: {message.JobDisplayName}");
|
||||
TaskResult result = jobContext.Complete(taskResult);
|
||||
if (jobContext.Global.Variables.TryGetValue(Constants.Runner.DeprecatedNodeDetectedAfterEndOfLifeActions, out var deprecatedNodeWarnings))
|
||||
{
|
||||
var actions = string.Join(", ", StringUtil.ConvertFromJson<HashSet<string>>(deprecatedNodeWarnings));
|
||||
jobContext.Warning(string.Format(Constants.Runner.DetectedNodeAfterEndOfLifeMessage, actions));
|
||||
}
|
||||
|
||||
if (jobContext.Global.Variables.TryGetValue(Constants.Runner.EnforcedNode12DetectedAfterEndOfLifeEnvVariable, out var node16ForceWarnings))
|
||||
var jobQueueTelemetry = await ShutdownQueue(throwOnFailure: false);
|
||||
// include any job telemetry from the background upload process.
|
||||
if (jobQueueTelemetry?.Count > 0)
|
||||
{
|
||||
var actions = string.Join(", ", StringUtil.ConvertFromJson<HashSet<string>>(node16ForceWarnings));
|
||||
jobContext.Warning(string.Format(Constants.Runner.EnforcedNode12DetectedAfterEndOfLife, actions));
|
||||
jobContext.Global.JobTelemetry.AddRange(jobQueueTelemetry);
|
||||
}
|
||||
|
||||
if (jobContext.Global.Variables.TryGetValue(Constants.Runner.EnforcedNode16DetectedAfterEndOfLifeEnvVariable, out var node20ForceWarnings) && (jobContext.Global.Variables.GetBoolean("DistributedTask.ForceGithubJavascriptActionsToNode20") ?? false))
|
||||
{
|
||||
var actions = string.Join(", ", StringUtil.ConvertFromJson<HashSet<string>>(node20ForceWarnings));
|
||||
jobContext.Warning(string.Format(Constants.Runner.EnforcedNode16DetectedAfterEndOfLife, actions));
|
||||
}
|
||||
|
||||
await ShutdownQueue(throwOnFailure: false);
|
||||
|
||||
// Make sure to clean temp after file upload since they may be pending fileupload still use the TEMP dir.
|
||||
_tempDirectoryManager?.CleanupTempDirectory();
|
||||
|
||||
@@ -314,6 +304,13 @@ namespace GitHub.Runner.Worker
|
||||
environmentUrl = urlStringToken.Value;
|
||||
}
|
||||
|
||||
// Get telemetry
|
||||
IList<Telemetry> telemetry = null;
|
||||
if (jobContext.Global.JobTelemetry.Count > 0)
|
||||
{
|
||||
telemetry = jobContext.Global.JobTelemetry.Select(x => new Telemetry { Type = x.Type.ToString(), Message = x.Message, }).ToList();
|
||||
}
|
||||
|
||||
Trace.Info($"Raising job completed against run service");
|
||||
var completeJobRetryLimit = 5;
|
||||
var exceptions = new List<Exception>();
|
||||
@@ -321,9 +318,23 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
try
|
||||
{
|
||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, result, jobContext.JobOutputs, jobContext.Global.StepsResult, jobContext.Global.JobAnnotations, environmentUrl, default);
|
||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, result, jobContext.JobOutputs, jobContext.Global.StepsResult, jobContext.Global.JobAnnotations, environmentUrl, telemetry, billingOwnerId: message.BillingOwnerId, default);
|
||||
return result;
|
||||
}
|
||||
catch (VssUnauthorizedException ex)
|
||||
{
|
||||
Trace.Error($"Catch exception while attempting to complete job {message.JobId}, job request {message.RequestId}.");
|
||||
Trace.Error(ex);
|
||||
exceptions.Add(ex);
|
||||
break;
|
||||
}
|
||||
catch (TaskOrchestrationJobNotFoundException ex)
|
||||
{
|
||||
Trace.Error($"Catch exception while attempting to complete job {message.JobId}, job request {message.RequestId}.");
|
||||
Trace.Error(ex);
|
||||
exceptions.Add(ex);
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error($"Catch exception while attempting to complete job {message.JobId}, job request {message.RequestId}.");
|
||||
@@ -346,74 +357,14 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
if (_runnerSettings.DisableUpdate == true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentVersion = new PackageVersion(BuildConstants.RunnerPackage.Version);
|
||||
ServiceEndpoint systemConnection = message.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
||||
VssCredentials serverCredential = VssUtil.GetVssCredential(systemConnection);
|
||||
|
||||
var runnerServer = HostContext.GetService<IRunnerServer>();
|
||||
await runnerServer.ConnectAsync(systemConnection.Url, serverCredential);
|
||||
var serverPackages = await runnerServer.GetPackagesAsync("agent", BuildConstants.RunnerPackage.PackageName, 5, includeToken: false, cancellationToken: CancellationToken.None);
|
||||
if (serverPackages.Count > 0)
|
||||
{
|
||||
serverPackages = serverPackages.OrderByDescending(x => x.Version).ToList();
|
||||
Trace.Info($"Newer packages {StringUtil.ConvertToJson(serverPackages.Select(x => x.Version.ToString()))}");
|
||||
|
||||
var warnOnFailedJob = false; // any minor/patch version behind.
|
||||
var warnOnOldRunnerVersion = false; // >= 2 minor version behind
|
||||
if (serverPackages.Any(x => x.Version.CompareTo(currentVersion) > 0))
|
||||
{
|
||||
Trace.Info($"Current runner version {currentVersion} is behind the latest runner version {serverPackages[0].Version}.");
|
||||
warnOnFailedJob = true;
|
||||
}
|
||||
|
||||
if (serverPackages.Where(x => x.Version.Major == currentVersion.Major && x.Version.Minor > currentVersion.Minor).Count() > 1)
|
||||
{
|
||||
Trace.Info($"Current runner version {currentVersion} is way behind the latest runner version {serverPackages[0].Version}.");
|
||||
warnOnOldRunnerVersion = true;
|
||||
}
|
||||
|
||||
if (result == TaskResult.Failed && warnOnFailedJob)
|
||||
{
|
||||
jobContext.Warning($"This job failure may be caused by using an out of date self-hosted runner. You are currently using runner version {currentVersion}. Please update to the latest version {serverPackages[0].Version}");
|
||||
}
|
||||
else if (warnOnOldRunnerVersion)
|
||||
{
|
||||
jobContext.Warning($"This self-hosted runner is currently using runner version {currentVersion}. This version is out of date. Please update to the latest version {serverPackages[0].Version}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Ignore any error since suggest runner update is best effort.
|
||||
Trace.Error($"Caught exception during runner version check: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
if (jobContext.Global.Variables.TryGetValue(Constants.Runner.DeprecatedNodeDetectedAfterEndOfLifeActions, out var deprecatedNodeWarnings))
|
||||
{
|
||||
var actions = string.Join(", ", StringUtil.ConvertFromJson<HashSet<string>>(deprecatedNodeWarnings));
|
||||
jobContext.Warning(string.Format(Constants.Runner.DetectedNodeAfterEndOfLifeMessage, actions));
|
||||
}
|
||||
|
||||
if (jobContext.Global.Variables.TryGetValue(Constants.Runner.EnforcedNode12DetectedAfterEndOfLifeEnvVariable, out var node16ForceWarnings))
|
||||
{
|
||||
var actions = string.Join(", ", StringUtil.ConvertFromJson<HashSet<string>>(node16ForceWarnings));
|
||||
jobContext.Warning(string.Format(Constants.Runner.EnforcedNode12DetectedAfterEndOfLife, actions));
|
||||
}
|
||||
|
||||
if (jobContext.Global.Variables.TryGetValue(Constants.Runner.EnforcedNode16DetectedAfterEndOfLifeEnvVariable, out var node20ForceWarnings))
|
||||
{
|
||||
var actions = string.Join(", ", StringUtil.ConvertFromJson<HashSet<string>>(node20ForceWarnings));
|
||||
jobContext.Warning(string.Format(Constants.Runner.EnforcedNode16DetectedAfterEndOfLife, actions));
|
||||
await WarningOutdatedRunnerAsync(jobContext, message, result);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var jobQueueTelemetry = await ShutdownQueue(throwOnFailure: true);
|
||||
// include any job telemetry from the background upload process.
|
||||
if (jobQueueTelemetry.Count > 0)
|
||||
if (jobQueueTelemetry?.Count > 0)
|
||||
{
|
||||
jobContext.Global.JobTelemetry.AddRange(jobQueueTelemetry);
|
||||
}
|
||||
@@ -541,5 +492,52 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
return Array.Empty<JobTelemetry>();
|
||||
}
|
||||
|
||||
private async Task WarningOutdatedRunnerAsync(IExecutionContext jobContext, Pipelines.AgentJobRequestMessage message, TaskResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentVersion = new PackageVersion(BuildConstants.RunnerPackage.Version);
|
||||
ServiceEndpoint systemConnection = message.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
||||
VssCredentials serverCredential = VssUtil.GetVssCredential(systemConnection);
|
||||
|
||||
var runnerServer = HostContext.GetService<IRunnerServer>();
|
||||
await runnerServer.ConnectAsync(systemConnection.Url, serverCredential);
|
||||
var serverPackages = await runnerServer.GetPackagesAsync("agent", BuildConstants.RunnerPackage.PackageName, 5, includeToken: false, cancellationToken: CancellationToken.None);
|
||||
if (serverPackages.Count > 0)
|
||||
{
|
||||
serverPackages = serverPackages.OrderByDescending(x => x.Version).ToList();
|
||||
Trace.Info($"Newer packages {StringUtil.ConvertToJson(serverPackages.Select(x => x.Version.ToString()))}");
|
||||
|
||||
var warnOnFailedJob = false; // any minor/patch version behind.
|
||||
var warnOnOldRunnerVersion = false; // >= 2 minor version behind
|
||||
if (serverPackages.Any(x => x.Version.CompareTo(currentVersion) > 0))
|
||||
{
|
||||
Trace.Info($"Current runner version {currentVersion} is behind the latest runner version {serverPackages[0].Version}.");
|
||||
warnOnFailedJob = true;
|
||||
}
|
||||
|
||||
if (serverPackages.Where(x => x.Version.Major == currentVersion.Major && x.Version.Minor > currentVersion.Minor).Count() > 1)
|
||||
{
|
||||
Trace.Info($"Current runner version {currentVersion} is way behind the latest runner version {serverPackages[0].Version}.");
|
||||
warnOnOldRunnerVersion = true;
|
||||
}
|
||||
|
||||
if (result == TaskResult.Failed && warnOnFailedJob)
|
||||
{
|
||||
jobContext.Warning($"This job failure may be caused by using an out of date version of GitHub runner on your self-hosted runner. You are currently using GitHub runner version {currentVersion}. Please update to the latest version {serverPackages[0].Version}");
|
||||
}
|
||||
else if (warnOnOldRunnerVersion)
|
||||
{
|
||||
jobContext.Warning($"This self-hosted runner is currently using runner version {currentVersion}. This version is out of date. Please update to the latest version {serverPackages[0].Version}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Ignore any error since suggest runner update is best effort.
|
||||
Trace.Error($"Caught exception during runner version check: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Sdk;
|
||||
|
||||
namespace GitHub.Runner.Worker
|
||||
{
|
||||
[ServiceLocator(Default = typeof(OSWarningChecker))]
|
||||
public interface IOSWarningChecker : IRunnerService
|
||||
{
|
||||
Task CheckOSAsync(IExecutionContext context);
|
||||
}
|
||||
|
||||
public sealed class OSWarningChecker : RunnerService, IOSWarningChecker
|
||||
{
|
||||
private static TimeSpan s_regexTimeout = TimeSpan.FromSeconds(1);
|
||||
|
||||
public async Task CheckOSAsync(IExecutionContext context)
|
||||
{
|
||||
ArgUtil.NotNull(context, nameof(context));
|
||||
if (!context.Global.Variables.System_TestDotNet8Compatibility)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
context.Output("Testing runner upgrade compatibility");
|
||||
List<string> output = new();
|
||||
object outputLock = new();
|
||||
try
|
||||
{
|
||||
using (var process = HostContext.CreateService<IProcessInvoker>())
|
||||
{
|
||||
process.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(stdout.Data))
|
||||
{
|
||||
lock (outputLock)
|
||||
{
|
||||
output.Add(stdout.Data);
|
||||
Trace.Info(stdout.Data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
process.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stderr)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(stderr.Data))
|
||||
{
|
||||
lock (outputLock)
|
||||
{
|
||||
output.Add(stderr.Data);
|
||||
Trace.Error(stderr.Data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using (var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(10)))
|
||||
{
|
||||
int exitCode = await process.ExecuteAsync(
|
||||
workingDirectory: HostContext.GetDirectory(WellKnownDirectory.Root),
|
||||
fileName: Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "testDotNet8Compatibility", $"TestDotNet8Compatibility{IOUtil.ExeExtension}"),
|
||||
arguments: string.Empty,
|
||||
environment: null,
|
||||
cancellationToken: cancellationTokenSource.Token);
|
||||
|
||||
var outputStr = string.Join("\n", output).Trim();
|
||||
if (exitCode != 0 || !string.Equals(outputStr, "Hello from .NET 8!", StringComparison.Ordinal))
|
||||
{
|
||||
var pattern = context.Global.Variables.System_DotNet8CompatibilityOutputPattern;
|
||||
if (!string.IsNullOrEmpty(pattern))
|
||||
{
|
||||
var regex = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant, s_regexTimeout);
|
||||
if (!regex.IsMatch(outputStr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var warningMessage = context.Global.Variables.System_DotNet8CompatibilityWarning;
|
||||
if (!string.IsNullOrEmpty(warningMessage))
|
||||
{
|
||||
context.Warning(warningMessage);
|
||||
}
|
||||
|
||||
context.Global.JobTelemetry.Add(new JobTelemetry() { Type = JobTelemetryType.General, Message = $".NET 8 OS compatibility test failed with exit code '{exitCode}' and output: {GetShortOutput(context, output)}" });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error("An error occurred while testing .NET 8 compatibility'");
|
||||
Trace.Error(ex);
|
||||
context.Global.JobTelemetry.Add(new JobTelemetry() { Type = JobTelemetryType.General, Message = $".NET 8 OS compatibility test encountered exception type '{ex.GetType().FullName}', message: '{ex.Message}', process output: '{GetShortOutput(context, output)}'" });
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetShortOutput(IExecutionContext context, List<string> output)
|
||||
{
|
||||
var length = context.Global.Variables.System_DotNet8CompatibilityOutputLength ?? 200;
|
||||
var outputStr = string.Join("\n", output).Trim();
|
||||
return outputStr.Length > length ? string.Concat(outputStr.Substring(0, length), "[...]") : outputStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64;win-arm64</RuntimeIdentifiers>
|
||||
<SelfContained>true</SelfContained>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<NoWarn>NU1701;NU1603</NoWarn>
|
||||
<NoWarn>NU1701;NU1603;SYSLIB0050;SYSLIB0051</NoWarn>
|
||||
<Version>$(Version)</Version>
|
||||
<PredefinedCulturesOnly>false</PredefinedCulturesOnly>
|
||||
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
|
||||
@@ -18,9 +19,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="4.4.0" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="4.4.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="8.0.1" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
|
||||
<PackageReference Include="YamlDotNet.Signed" Version="5.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -72,16 +72,8 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
public bool? Step_Debug => GetBoolean(Constants.Variables.Actions.StepDebug);
|
||||
|
||||
public string System_DotNet8CompatibilityWarning => Get(Constants.Variables.System.DotNet8CompatibilityWarning);
|
||||
|
||||
public string System_DotNet8CompatibilityOutputPattern => Get(Constants.Variables.System.DotNet8CompatibilityOutputPattern);
|
||||
|
||||
public int? System_DotNet8CompatibilityOutputLength => GetInt(Constants.Variables.System.DotNet8CompatibilityOutputLength);
|
||||
|
||||
public string System_PhaseDisplayName => Get(Constants.Variables.System.PhaseDisplayName);
|
||||
|
||||
public bool System_TestDotNet8Compatibility => GetBoolean(Constants.Variables.System.TestDotNet8Compatibility) ?? false;
|
||||
|
||||
public string Get(string name)
|
||||
{
|
||||
Variable variable;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Tracing;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Sockets;
|
||||
@@ -335,7 +336,25 @@ namespace GitHub.Services.Common.Diagnostics
|
||||
if (IsEnabled())
|
||||
{
|
||||
SetActivityId(activity);
|
||||
HttpRequestStop(response.RequestMessage.GetHttpMethod(), response.RequestMessage.RequestUri.AbsoluteUri, (Int32)response.StatusCode);
|
||||
var requestId = "NoExpectedHeader";
|
||||
if (response.Headers != null)
|
||||
{
|
||||
if (response.Headers.TryGetValues("x-github-request-id", out var headerValues) && headerValues != null)
|
||||
{
|
||||
requestId = headerValues.FirstOrDefault();
|
||||
}
|
||||
else if (response.Headers.TryGetValues("x-vss-e2eid", out headerValues) && headerValues != null)
|
||||
{
|
||||
requestId = headerValues.FirstOrDefault();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(requestId))
|
||||
{
|
||||
requestId = "NoExpectedHeader";
|
||||
}
|
||||
}
|
||||
|
||||
HttpRequestStop(response.RequestMessage.GetHttpMethod(), response.RequestMessage.RequestUri.AbsoluteUri, (Int32)response.StatusCode, requestId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -747,15 +766,16 @@ namespace GitHub.Services.Common.Diagnostics
|
||||
}
|
||||
}
|
||||
|
||||
[Event(24, Level = EventLevel.Verbose, Task = Tasks.HttpRequest, Opcode = EventOpcode.Stop, Message = "Finished {0} request to {1} with status code {2}")]
|
||||
[Event(24, Level = EventLevel.Verbose, Task = Tasks.HttpRequest, Opcode = EventOpcode.Stop, Message = "Finished {0} request to {1} with status code {2} ({3})")]
|
||||
private void HttpRequestStop(
|
||||
VssHttpMethod method,
|
||||
String url,
|
||||
Int32 statusCode)
|
||||
Int32 statusCode,
|
||||
String requestId)
|
||||
{
|
||||
if (IsEnabled())
|
||||
{
|
||||
WriteEvent(24, (Int32)method, url, statusCode);
|
||||
WriteEvent(24, (Int32)method, url, statusCode, requestId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace GitHub.Services.Common
|
||||
|
||||
public String PropertyName { get; set; }
|
||||
|
||||
[Obsolete]
|
||||
[SecurityCritical]
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
|
||||
@@ -127,6 +127,7 @@ namespace GitHub.Services.Common
|
||||
EventId = (int)info.GetValue("m_eventId", typeof(int));
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
[SecurityCritical]
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
|
||||
@@ -23,8 +23,8 @@ using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Formatting;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Services.Common;
|
||||
@@ -827,5 +827,36 @@ namespace GitHub.DistributedTask.WebApi
|
||||
userState: userState,
|
||||
cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [Preview API]
|
||||
/// </summary>
|
||||
/// <param name="agentId"></param>
|
||||
/// <param name="configType"></param>
|
||||
/// <param name="encodedRunnerConfig"></param>
|
||||
/// <param name="userState"></param>
|
||||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public virtual Task<string> RefreshRunnerConfigAsync(
|
||||
int agentId,
|
||||
string configType,
|
||||
string encodedRunnerConfig,
|
||||
object userState = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
HttpMethod httpMethod = new HttpMethod("POST");
|
||||
Guid locationId = new Guid("13b5d709-74aa-470b-a8e9-bf9f3ded3f18");
|
||||
object routeValues = new { agentId = agentId, configType = configType };
|
||||
HttpContent content = new ObjectContent<string>(encodedRunnerConfig, new VssJsonMediaTypeFormatter(true));
|
||||
|
||||
return SendAsync<string>(
|
||||
httpMethod,
|
||||
locationId,
|
||||
routeValues: routeValues,
|
||||
version: new ApiResourceVersion(6.0, 1),
|
||||
userState: userState,
|
||||
cancellationToken: cancellationToken,
|
||||
content: content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,6 +246,13 @@ namespace GitHub.DistributedTask.Pipelines
|
||||
set;
|
||||
}
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String BillingOwnerId
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of variables associated with the current context.
|
||||
/// </summary>
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
||||
{
|
||||
if (!NameValidation.IsValid(value, allowHyphens: true) && value.Length < PipelineConstants.MaxNodeNameLength)
|
||||
{
|
||||
error = $"The identifier '{value}' is invalid. IDs may only contain alphanumeric characters, '_', and '-'. IDs must start with a letter or '_' and and must be less than {PipelineConstants.MaxNodeNameLength} characters.";
|
||||
error = $"The identifier '{value}' is invalid. IDs may only contain alphanumeric characters, '_', and '-'. IDs must start with a letter or '_' and must be less than {PipelineConstants.MaxNodeNameLength} characters.";
|
||||
return false;
|
||||
}
|
||||
else if (!m_distinctNames.Add(value))
|
||||
|
||||
@@ -9,6 +9,9 @@ namespace GitHub.DistributedTask.WebApi
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public ActionDownloadAuthentication Authentication { get; set; }
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public ActionDownloadPackageDetails PackageDetails { get; set; }
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public string NameWithOwner { get; set; }
|
||||
|
||||
@@ -37,4 +40,14 @@ namespace GitHub.DistributedTask.WebApi
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public string Token { get; set; }
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public class ActionDownloadPackageDetails
|
||||
{
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public string Version { get; set; }
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public string ManifestDigest { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The url to connect to to poll for messages
|
||||
/// The url to connect to poll for messages
|
||||
/// </summary>
|
||||
[JsonProperty("server_url")]
|
||||
public string ServerUrl
|
||||
|
||||
58
src/Sdk/DTWebApi/WebApi/RunnerRefreshConfigMessage.cs
Normal file
58
src/Sdk/DTWebApi/WebApi/RunnerRefreshConfigMessage.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.Services.WebApi;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace GitHub.DistributedTask.WebApi
|
||||
{
|
||||
[DataContract]
|
||||
public sealed class RunnerRefreshConfigMessage
|
||||
{
|
||||
public static readonly String MessageType = "RunnerRefreshConfig";
|
||||
|
||||
[JsonConstructor]
|
||||
internal RunnerRefreshConfigMessage()
|
||||
{
|
||||
}
|
||||
|
||||
public RunnerRefreshConfigMessage(
|
||||
string runnerQualifiedId,
|
||||
string configType,
|
||||
string serviceType,
|
||||
string configRefreshUrl)
|
||||
{
|
||||
this.RunnerQualifiedId = runnerQualifiedId;
|
||||
this.ConfigType = configType;
|
||||
this.ServiceType = serviceType;
|
||||
this.ConfigRefreshUrl = configRefreshUrl;
|
||||
}
|
||||
|
||||
[DataMember(Name = "runnerQualifiedId")]
|
||||
public String RunnerQualifiedId
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[DataMember(Name = "configType")]
|
||||
public String ConfigType
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[DataMember(Name = "serviceType")]
|
||||
public String ServiceType
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[DataMember(Name = "configRefreshURL")]
|
||||
public String ConfigRefreshUrl
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/Sdk/DTWebApi/WebApi/ServiceConnectivityCheck.cs
Normal file
42
src/Sdk/DTWebApi/WebApi/ServiceConnectivityCheck.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace GitHub.DistributedTask.WebApi
|
||||
{
|
||||
[DataContract]
|
||||
public class ServiceConnectivityCheckInput
|
||||
{
|
||||
[JsonConstructor]
|
||||
public ServiceConnectivityCheckInput()
|
||||
{
|
||||
Endpoints = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public Dictionary<string, string> Endpoints { get; set; }
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public int IntervalInSecond { get; set; }
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public int RequestTimeoutInSecond { get; set; }
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public class ServiceConnectivityCheckResult
|
||||
{
|
||||
[JsonConstructor]
|
||||
public ServiceConnectivityCheckResult()
|
||||
{
|
||||
EndpointsResult = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[DataMember(Order = 1, EmitDefaultValue = true)]
|
||||
public bool HasFailure { get; set; }
|
||||
|
||||
[DataMember(Order = 2, EmitDefaultValue = false)]
|
||||
public Dictionary<string, List<string>> EndpointsResult { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -7,5 +7,6 @@ namespace GitHub.DistributedTask.WebApi
|
||||
public static readonly String JobId = "system.jobId";
|
||||
public static readonly String RunnerLowDiskspaceThreshold = "system.runner.lowdiskspacethreshold";
|
||||
public static readonly String RunnerEnvironment = "system.runnerEnvironment";
|
||||
public static readonly String RunnerServiceConnectivityTest = "system.runner.serviceconnectivitycheckinput";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,5 +10,8 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
|
||||
[DataMember(Name = "runnerOS", EmitDefaultValue = false)]
|
||||
public string RunnerOS { get; set; }
|
||||
|
||||
[DataMember(Name = "billingOwnerId", EmitDefaultValue = false)]
|
||||
public string BillingOwnerId { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ namespace Sdk.RSWebApi.Contracts
|
||||
[DataMember(Name = "message", EmitDefaultValue = false)]
|
||||
public string Message;
|
||||
|
||||
[DataMember(Name = "title", EmitDefaultValue = false)]
|
||||
public string Title;
|
||||
|
||||
[DataMember(Name = "rawDetails", EmitDefaultValue = false)]
|
||||
public string RawDetails;
|
||||
|
||||
@@ -31,5 +34,8 @@ namespace Sdk.RSWebApi.Contracts
|
||||
|
||||
[DataMember(Name = "endColumn", EmitDefaultValue = false)]
|
||||
public long EndColumn;
|
||||
|
||||
[DataMember(Name = "stepNumber", EmitDefaultValue = false)]
|
||||
public long StepNumber;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
[DataContract]
|
||||
public class BrokerErrorKind
|
||||
{
|
||||
public const string RunnerNotFound = "RunnerNotFound";
|
||||
public const string RunnerVersionTooOld = "RunnerVersionTooOld";
|
||||
public const string HostedRunnerDeprovisioned = "HostedRunnerDeprovisioned";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,13 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
[DataMember(Name = "annotations", EmitDefaultValue = false)]
|
||||
public IList<Annotation> Annotations { get; set; }
|
||||
|
||||
[DataMember(Name = "telemetry", EmitDefaultValue = false)]
|
||||
public IList<Telemetry> Telemetry { get; set; }
|
||||
|
||||
[DataMember(Name = "environmentUrl", EmitDefaultValue = false)]
|
||||
public string EnvironmentUrl { get; set; }
|
||||
|
||||
[DataMember(Name = "billingOwnerId", EmitDefaultValue = false)]
|
||||
public string BillingOwnerId { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ namespace Sdk.RSWebApi.Contracts
|
||||
var columnNumber = GetAnnotationNumber(issue, RunIssueKeys.Col) ?? 0;
|
||||
var endColumnNumber = GetAnnotationNumber(issue, RunIssueKeys.EndColumn) ?? columnNumber;
|
||||
var logLineNumber = GetAnnotationNumber(issue, RunIssueKeys.LogLineNumber) ?? 0;
|
||||
var stepNumber = GetAnnotationNumber(issue, RunIssueKeys.StepNumber) ?? 0;
|
||||
var title = GetAnnotationField(issue, RunIssueKeys.Title);
|
||||
|
||||
if (path == null && lineNumber == 0 && logLineNumber != 0)
|
||||
{
|
||||
@@ -33,11 +35,13 @@ namespace Sdk.RSWebApi.Contracts
|
||||
{
|
||||
Level = annotationLevel,
|
||||
Message = issueMessage,
|
||||
Title = title,
|
||||
Path = path,
|
||||
StartLine = lineNumber,
|
||||
EndLine = endLineNumber,
|
||||
StartColumn = columnNumber,
|
||||
EndColumn = endColumnNumber,
|
||||
StepNumber = stepNumber,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -9,5 +9,7 @@
|
||||
public const string EndLine = "endLine";
|
||||
public const string EndColumn = "endColumn";
|
||||
public const string LogLineNumber = "logFileLineNumber";
|
||||
public const string StepNumber = "stepNumber";
|
||||
public const string Title = "title";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
@@ -16,9 +16,20 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
[DataMember(Name = "number", EmitDefaultValue = false)]
|
||||
public int? Number { get; set; }
|
||||
|
||||
// Example: "Run actions/checkout@v3"
|
||||
[DataMember(Name = "name", EmitDefaultValue = false)]
|
||||
public string Name { get; set; }
|
||||
|
||||
// Example: "actions/checkout"
|
||||
[DataMember(Name = "action_name", EmitDefaultValue = false)]
|
||||
public string ActionName { get; set; }
|
||||
|
||||
[DataMember(Name = "ref", EmitDefaultValue = false)]
|
||||
public string Ref { get; set; }
|
||||
|
||||
[DataMember(Name = "type", EmitDefaultValue = false)]
|
||||
public string Type { get; set; }
|
||||
|
||||
[DataMember(Name = "status")]
|
||||
public TimelineRecordState? Status { get; set; }
|
||||
|
||||
|
||||
20
src/Sdk/RSWebApi/Contracts/Telemetry.cs
Normal file
20
src/Sdk/RSWebApi/Contracts/Telemetry.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Sdk.RSWebApi.Contracts
|
||||
{
|
||||
[DataContract]
|
||||
public struct Telemetry
|
||||
{
|
||||
public Telemetry(string message, string type)
|
||||
{
|
||||
Message = message;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
[DataMember(Name = "message", EmitDefaultValue = false)]
|
||||
public string Message { get; set; }
|
||||
|
||||
[DataMember(Name = "type", EmitDefaultValue = false)]
|
||||
public string Type { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
@@ -70,13 +71,15 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
Uri requestUri,
|
||||
string messageId,
|
||||
string runnerOS,
|
||||
string billingOwnerId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
HttpMethod httpMethod = new HttpMethod("POST");
|
||||
var payload = new AcquireJobRequest
|
||||
{
|
||||
JobMessageId = messageId,
|
||||
RunnerOS = runnerOS
|
||||
RunnerOS = runnerOS,
|
||||
BillingOwnerId = billingOwnerId,
|
||||
};
|
||||
|
||||
requestUri = new Uri(requestUri, "acquirejob");
|
||||
@@ -126,6 +129,8 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
IList<StepResult> stepResults,
|
||||
IList<Annotation> jobAnnotations,
|
||||
string environmentUrl,
|
||||
IList<Telemetry> telemetry,
|
||||
string billingOwnerId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
HttpMethod httpMethod = new HttpMethod("POST");
|
||||
@@ -138,6 +143,8 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
StepResults = stepResults,
|
||||
Annotations = jobAnnotations,
|
||||
EnvironmentUrl = environmentUrl,
|
||||
Telemetry = telemetry,
|
||||
BillingOwnerId = billingOwnerId,
|
||||
};
|
||||
|
||||
requestUri = new Uri(requestUri, "completejob");
|
||||
@@ -246,11 +253,12 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string Truncate(string errorBody)
|
||||
internal static string Truncate(string errorBody)
|
||||
{
|
||||
if (errorBody.Length > 100)
|
||||
const int maxLength = 200;
|
||||
if (errorBody.Length > maxLength)
|
||||
{
|
||||
return errorBody.Substring(0, 100) + "[truncated]";
|
||||
return errorBody.Substring(0, maxLength) + "[truncated]";
|
||||
}
|
||||
|
||||
return errorBody;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<OutputType>Library</OutputType>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64;win-arm64</RuntimeIdentifiers>
|
||||
<!-- <SelfContained>true</SelfContained> -->
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<NoWarn>NU1701;NU1603</NoWarn>
|
||||
<NoWarn>NU1701;NU1603;SYSLIB0050;SYSLIB0051</NoWarn>
|
||||
<Version>$(Version)</Version>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
@@ -13,18 +14,19 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.19.1" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" />
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.24.0" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.9" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.2.1" />
|
||||
<PackageReference Include="System.Security.Cryptography.Cng" Version="4.4.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="4.4.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="8.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
||||
<PackageReference Include="Minimatch" Version="2.0.0" />
|
||||
<PackageReference Include="YamlDotNet.Signed" Version="5.3.0" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
|
||||
<PackageReference Include="System.Formats.Asn1" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -115,11 +115,15 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
{
|
||||
switch (brokerError.ErrorKind)
|
||||
{
|
||||
case BrokerErrorKind.RunnerNotFound:
|
||||
throw new RunnerNotFoundException(brokerError.Message);
|
||||
case BrokerErrorKind.RunnerVersionTooOld:
|
||||
throw new AccessDeniedException(brokerError.Message)
|
||||
{
|
||||
ErrorCode = 1
|
||||
};
|
||||
case BrokerErrorKind.HostedRunnerDeprovisioned:
|
||||
throw new HostedRunnerDeprovisionedException(brokerError.Message);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
namespace GitHub.Services.WebApi
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class HostedRunnerDeprovisionedException : Exception
|
||||
{
|
||||
public HostedRunnerDeprovisionedException()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public HostedRunnerDeprovisionedException(String message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public HostedRunnerDeprovisionedException(String message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/Sdk/WebApi/WebApi/Exceptions/RunnerNotFoundException.cs
Normal file
26
src/Sdk/WebApi/WebApi/Exceptions/RunnerNotFoundException.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.WebApi;
|
||||
|
||||
namespace GitHub.Services.WebApi
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class RunnerNotFoundException : Exception
|
||||
{
|
||||
public RunnerNotFoundException()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public RunnerNotFoundException(String message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public RunnerNotFoundException(String message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,10 @@ namespace GitHub.Services.WebApi.Jwt
|
||||
HS256,
|
||||
|
||||
[EnumMember]
|
||||
RS256
|
||||
RS256,
|
||||
|
||||
[EnumMember]
|
||||
PS256,
|
||||
}
|
||||
|
||||
//JsonWebToken is marked as DataContract so
|
||||
@@ -286,6 +289,7 @@ namespace GitHub.Services.WebApi.Jwt
|
||||
{
|
||||
case JWTAlgorithm.HS256:
|
||||
case JWTAlgorithm.RS256:
|
||||
case JWTAlgorithm.PS256:
|
||||
return signingCredentials.SignData(bytes);
|
||||
|
||||
default:
|
||||
|
||||
@@ -30,6 +30,9 @@ namespace GitHub.Services.Launch.Contracts
|
||||
[DataMember(EmitDefaultValue = false, Name = "authentication")]
|
||||
public ActionDownloadAuthenticationResponse Authentication { get; set; }
|
||||
|
||||
[DataMember(EmitDefaultValue = false, Name = "package_details")]
|
||||
public ActionDownloadPackageDetailsResponse PackageDetails { get; set; }
|
||||
|
||||
[DataMember(EmitDefaultValue = false, Name = "name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
@@ -59,6 +62,17 @@ namespace GitHub.Services.Launch.Contracts
|
||||
public string Token { get; set; }
|
||||
}
|
||||
|
||||
|
||||
[DataContract]
|
||||
public class ActionDownloadPackageDetailsResponse
|
||||
{
|
||||
[DataMember(EmitDefaultValue = false, Name = "version")]
|
||||
public string Version { get; set; }
|
||||
|
||||
[DataMember(EmitDefaultValue = false, Name = "manifest_digest")]
|
||||
public string ManifestDigest { get; set; }
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public class ActionDownloadInfoResponseCollection
|
||||
{
|
||||
|
||||
@@ -91,6 +91,7 @@ namespace GitHub.Services.Launch.Client
|
||||
TarballUrl = actionDownloadInfoResponse.TarUrl,
|
||||
Ref = actionDownloadInfoResponse.Version,
|
||||
ZipballUrl = actionDownloadInfoResponse.ZipUrl,
|
||||
PackageDetails = ToServerData(actionDownloadInfoResponse.PackageDetails)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -108,6 +109,21 @@ namespace GitHub.Services.Launch.Client
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private static ActionDownloadPackageDetails? ToServerData(ActionDownloadPackageDetailsResponse? actionDownloadPackageDetails)
|
||||
{
|
||||
if (actionDownloadPackageDetails == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ActionDownloadPackageDetails
|
||||
{
|
||||
Version = actionDownloadPackageDetails.Version,
|
||||
ManifestDigest = actionDownloadPackageDetails.ManifestDigest
|
||||
};
|
||||
}
|
||||
|
||||
private MediaTypeFormatter m_formatter;
|
||||
private Uri m_launchServiceUrl;
|
||||
private string m_token;
|
||||
|
||||
@@ -677,7 +677,7 @@ namespace GitHub.Services.WebApi.Location
|
||||
Int32 lastChangeId = m_locationDataCacheManager.GetLastChangeId();
|
||||
|
||||
// If we have -1 then that means we have no disk cache yet or it means that we recently hit an exception trying to reload
|
||||
// the the cache from disk (see Exception catch block in EnsureDiskCacheLoaded).
|
||||
// the cache from disk (see Exception catch block in EnsureDiskCacheLoaded).
|
||||
// Either way, we cannot make a call to the server with -1 and pass None.
|
||||
// If we do, the resulting payload (which would have ClientCacheFresh=false but include no ServiceDefinitions)
|
||||
// would leave the in-memory cache in an inconsistent state
|
||||
|
||||
@@ -85,6 +85,7 @@ namespace GitHub.Services.OAuth
|
||||
set;
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
base.GetObjectData(info, context);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
@@ -132,7 +133,7 @@ namespace GitHub.Services.Results.Client
|
||||
|
||||
private async Task StepSummaryUploadCompleteAsync(string planId, string jobId, Guid stepId, long size, CancellationToken cancellationToken)
|
||||
{
|
||||
var timestamp = DateTime.UtcNow.ToString(Constants.TimestampFormat);
|
||||
var timestamp = DateTime.UtcNow.ToString(Constants.TimestampFormat, CultureInfo.InvariantCulture);
|
||||
var request = new StepSummaryMetadataCreate()
|
||||
{
|
||||
WorkflowJobRunBackendId = jobId,
|
||||
@@ -148,7 +149,7 @@ namespace GitHub.Services.Results.Client
|
||||
|
||||
private async Task StepLogUploadCompleteAsync(string planId, string jobId, Guid stepId, long lineCount, CancellationToken cancellationToken)
|
||||
{
|
||||
var timestamp = DateTime.UtcNow.ToString(Constants.TimestampFormat);
|
||||
var timestamp = DateTime.UtcNow.ToString(Constants.TimestampFormat, CultureInfo.InvariantCulture);
|
||||
var request = new StepLogsMetadataCreate()
|
||||
{
|
||||
WorkflowJobRunBackendId = jobId,
|
||||
@@ -164,7 +165,7 @@ namespace GitHub.Services.Results.Client
|
||||
|
||||
private async Task JobLogUploadCompleteAsync(string planId, string jobId, long lineCount, CancellationToken cancellationToken)
|
||||
{
|
||||
var timestamp = DateTime.UtcNow.ToString(Constants.TimestampFormat);
|
||||
var timestamp = DateTime.UtcNow.ToString(Constants.TimestampFormat, CultureInfo.InvariantCulture);
|
||||
var request = new JobLogsMetadataCreate()
|
||||
{
|
||||
WorkflowJobRunBackendId = jobId,
|
||||
@@ -565,7 +566,7 @@ namespace GitHub.Services.Results.Client
|
||||
|
||||
public async Task UpdateWorkflowStepsAsync(Guid planId, IEnumerable<TimelineRecord> records, CancellationToken cancellationToken)
|
||||
{
|
||||
var timestamp = DateTime.UtcNow.ToString(Constants.TimestampFormat);
|
||||
var timestamp = DateTime.UtcNow.ToString(Constants.TimestampFormat, CultureInfo.InvariantCulture);
|
||||
var stepRecords = records.Where(r => String.Equals(r.RecordType, "Task", StringComparison.Ordinal));
|
||||
var stepUpdateRequests = stepRecords.GroupBy(r => r.ParentId).Select(sg => new StepsUpdateRequest()
|
||||
{
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace GitHub.Services.WebApi
|
||||
HttpStatusCode = (HttpStatusCode)info.GetInt32("HttpStatusCode");
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
[SecurityCritical]
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
|
||||
@@ -166,6 +166,21 @@ namespace GitHub.Services.WebApi
|
||||
}
|
||||
}
|
||||
|
||||
public override JWTAlgorithm SignatureAlgorithm
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_signaturePadding == RSASignaturePadding.Pss)
|
||||
{
|
||||
return JWTAlgorithm.PS256;
|
||||
}
|
||||
else
|
||||
{
|
||||
return base.SignatureAlgorithm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override Byte[] GetSignature(Byte[] input)
|
||||
{
|
||||
using (var rsa = m_factory())
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using GitHub.Runner.Common.Util;
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests
|
||||
@@ -172,6 +172,133 @@ namespace GitHub.Runner.Common.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void AuthMigrationDisabledByDefault()
|
||||
{
|
||||
try
|
||||
{
|
||||
Environment.SetEnvironmentVariable("_GITHUB_ACTION_AUTH_MIGRATION_REFRESH_INTERVAL", "100");
|
||||
|
||||
// Arrange.
|
||||
Setup();
|
||||
|
||||
// Assert.
|
||||
Assert.False(_hc.AllowAuthMigration);
|
||||
|
||||
// Change migration state is error free.
|
||||
_hc.EnableAuthMigration("L0Test");
|
||||
_hc.DeferAuthMigration(TimeSpan.FromHours(1), "L0Test");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Environment.SetEnvironmentVariable("_GITHUB_ACTION_AUTH_MIGRATION_REFRESH_INTERVAL", null);
|
||||
// Cleanup.
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public async Task AuthMigrationReenableTaskNotRunningByDefault()
|
||||
{
|
||||
try
|
||||
{
|
||||
Environment.SetEnvironmentVariable("_GITHUB_ACTION_AUTH_MIGRATION_REFRESH_INTERVAL", "50");
|
||||
|
||||
// Arrange.
|
||||
Setup();
|
||||
|
||||
// Assert.
|
||||
Assert.False(_hc.AllowAuthMigration);
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(200));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Environment.SetEnvironmentVariable("_GITHUB_ACTION_AUTH_MIGRATION_REFRESH_INTERVAL", null);
|
||||
// Cleanup.
|
||||
Teardown();
|
||||
}
|
||||
|
||||
var logFile = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"trace_{nameof(HostContextL0)}_{nameof(AuthMigrationReenableTaskNotRunningByDefault)}.log");
|
||||
var logContent = await File.ReadAllTextAsync(logFile);
|
||||
Assert.Contains("HostContext", logContent);
|
||||
Assert.DoesNotContain("Auth migration defer timer", logContent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public void AuthMigrationEnableDisable()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Arrange.
|
||||
Setup();
|
||||
|
||||
var eventFiredCount = 0;
|
||||
_hc.AuthMigrationChanged += (sender, e) =>
|
||||
{
|
||||
eventFiredCount++;
|
||||
Assert.Equal("L0Test", e.Trace);
|
||||
};
|
||||
|
||||
// Assert.
|
||||
_hc.EnableAuthMigration("L0Test");
|
||||
Assert.True(_hc.AllowAuthMigration);
|
||||
|
||||
_hc.DeferAuthMigration(TimeSpan.FromHours(1), "L0Test");
|
||||
Assert.False(_hc.AllowAuthMigration);
|
||||
Assert.Equal(2, eventFiredCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup.
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Common")]
|
||||
public async Task AuthMigrationAutoReset()
|
||||
{
|
||||
try
|
||||
{
|
||||
Environment.SetEnvironmentVariable("_GITHUB_ACTION_AUTH_MIGRATION_REFRESH_INTERVAL", "100");
|
||||
|
||||
// Arrange.
|
||||
Setup();
|
||||
|
||||
var eventFiredCount = 0;
|
||||
_hc.AuthMigrationChanged += (sender, e) =>
|
||||
{
|
||||
eventFiredCount++;
|
||||
Assert.NotEmpty(e.Trace);
|
||||
};
|
||||
|
||||
// Assert.
|
||||
_hc.EnableAuthMigration("L0Test");
|
||||
Assert.True(_hc.AllowAuthMigration);
|
||||
|
||||
_hc.DeferAuthMigration(TimeSpan.FromMilliseconds(500), "L0Test");
|
||||
Assert.False(_hc.AllowAuthMigration);
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
Assert.True(_hc.AllowAuthMigration);
|
||||
Assert.Equal(3, eventFiredCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Environment.SetEnvironmentVariable("_GITHUB_ACTION_AUTH_MIGRATION_REFRESH_INTERVAL", null);
|
||||
|
||||
// Cleanup.
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
private void Setup([CallerMemberName] string testName = "")
|
||||
{
|
||||
_tokenSource = new CancellationTokenSource();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user