mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:36:23 +00:00
Compare commits
54 Commits
v2.321.0
...
users/tihu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd127e6a90 | ||
|
|
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 |
@@ -4,7 +4,7 @@
|
|||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
||||||
"ghcr.io/devcontainers/features/dotnet": {
|
"ghcr.io/devcontainers/features/dotnet": {
|
||||||
"version": "8.0.404"
|
"version": "8.0.408"
|
||||||
},
|
},
|
||||||
"ghcr.io/devcontainers/features/node:1": {
|
"ghcr.io/devcontainers/features/node:1": {
|
||||||
"version": "20"
|
"version": "20"
|
||||||
|
|||||||
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
|
url: https://github.community/c/code-to-cloud/52
|
||||||
about: If you have questions about GitHub Actions or need support writing workflows, please ask in the GitHub Community Support forum.
|
about: If you have questions about GitHub Actions or need support writing workflows, please ask in the GitHub Community Support forum.
|
||||||
- name: ✅ Feedback and suggestions for GitHub Actions
|
- 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.
|
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
|
- name: ‼️ GitHub Security Bug Bounty
|
||||||
url: https://bounty.github.com/
|
url: https://bounty.github.com/
|
||||||
|
|||||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -69,7 +69,7 @@ jobs:
|
|||||||
- name: Package Release
|
- name: Package Release
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
run: |
|
run: |
|
||||||
${{ matrix.devScript }} package Release
|
${{ matrix.devScript }} package Release ${{ matrix.runtime }}
|
||||||
working-directory: src
|
working-directory: src
|
||||||
|
|
||||||
# Upload runner package tar.gz/zip as artifact
|
# 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:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v8
|
- uses: actions/stale@v9
|
||||||
with:
|
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. 😃"
|
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"
|
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:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v8
|
- uses: actions/stale@v9
|
||||||
with:
|
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). 😃"
|
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"
|
exempt-issue-labels: "keep"
|
||||||
|
|||||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v3
|
||||||
# Override language selection by uncommenting this and choosing your languages
|
# Override language selection by uncommenting this and choosing your languages
|
||||||
# with:
|
# with:
|
||||||
# languages: go, javascript, csharp, python, cpp, java
|
# languages: go, javascript, csharp, python, cpp, java
|
||||||
@@ -38,4 +38,4 @@ jobs:
|
|||||||
working-directory: src
|
working-directory: src
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v3
|
||||||
|
|||||||
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@v4
|
|
||||||
|
|
||||||
- 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
|
|
||||||
25
.github/workflows/release.yml
vendored
25
.github/workflows/release.yml
vendored
@@ -16,11 +16,10 @@ jobs:
|
|||||||
# Make sure ./releaseVersion match ./src/runnerversion
|
# Make sure ./releaseVersion match ./src/runnerversion
|
||||||
# Query GitHub release ensure version is not used
|
# Query GitHub release ensure version is not used
|
||||||
- name: Check version
|
- name: Check version
|
||||||
uses: actions/github-script@0.3.0
|
uses: actions/github-script@v7.0.1
|
||||||
with:
|
with:
|
||||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||||
script: |
|
script: |
|
||||||
const core = require('@actions/core')
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const runnerVersion = fs.readFileSync('${{ github.workspace }}/src/runnerversion', 'utf8').replace(/\n$/g, '')
|
const runnerVersion = fs.readFileSync('${{ github.workspace }}/src/runnerversion', 'utf8').replace(/\n$/g, '')
|
||||||
const releaseVersion = fs.readFileSync('${{ github.workspace }}/releaseVersion', 'utf8').replace(/\n$/g, '')
|
const releaseVersion = fs.readFileSync('${{ github.workspace }}/releaseVersion', 'utf8').replace(/\n$/g, '')
|
||||||
@@ -30,7 +29,7 @@ jobs:
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const release = await github.repos.getReleaseByTag({
|
const release = await github.rest.repos.getReleaseByTag({
|
||||||
owner: '${{ github.event.repository.owner.name }}',
|
owner: '${{ github.event.repository.owner.name }}',
|
||||||
repo: '${{ github.event.repository.name }}',
|
repo: '${{ github.event.repository.name }}',
|
||||||
tag: 'v' + runnerVersion
|
tag: 'v' + runnerVersion
|
||||||
@@ -172,11 +171,10 @@ jobs:
|
|||||||
# Create ReleaseNote file
|
# Create ReleaseNote file
|
||||||
- name: Create ReleaseNote
|
- name: Create ReleaseNote
|
||||||
id: releaseNote
|
id: releaseNote
|
||||||
uses: actions/github-script@0.3.0
|
uses: actions/github-script@v7.0.1
|
||||||
with:
|
with:
|
||||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||||
script: |
|
script: |
|
||||||
const core = require('@actions/core')
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const runnerVersion = fs.readFileSync('${{ github.workspace }}/src/runnerversion', 'utf8').replace(/\n$/g, '')
|
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)
|
var releaseNote = fs.readFileSync('${{ github.workspace }}/releaseNote.md', 'utf8').replace(/<RUNNER_VERSION>/g, runnerVersion)
|
||||||
@@ -291,6 +289,8 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
id-token: write
|
||||||
|
attestations: write
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
IMAGE_NAME: ${{ github.repository_owner }}/actions-runner
|
IMAGE_NAME: ${{ github.repository_owner }}/actions-runner
|
||||||
@@ -300,7 +300,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Compute image version
|
- name: Compute image version
|
||||||
id: image
|
id: image
|
||||||
uses: actions/github-script@v6
|
uses: actions/github-script@v7.0.1
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
@@ -309,10 +309,10 @@ jobs:
|
|||||||
core.setOutput('version', runnerVersion);
|
core.setOutput('version', runnerVersion);
|
||||||
|
|
||||||
- name: Setup Docker buildx
|
- name: Setup Docker buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Log into registry ${{ env.REGISTRY }}
|
- name: Log into registry ${{ env.REGISTRY }}
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -320,7 +320,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
id: build-and-push
|
id: build-and-push
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: ./images
|
context: ./images
|
||||||
platforms: |
|
platforms: |
|
||||||
@@ -336,3 +336,10 @@ jobs:
|
|||||||
org.opencontainers.image.source=${{github.server_url}}/${{github.repository}}
|
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.description=https://github.com/actions/runner/releases/tag/v${{ steps.image.outputs.version }}
|
||||||
org.opencontainers.image.licenses=MIT
|
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:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v8
|
- uses: actions/stale@v9
|
||||||
with:
|
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."
|
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."
|
close-issue-message: "This issue was closed because it has been stalled for 15 days with no activity."
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ This feature is mainly intended for self hosted runner administrators.
|
|||||||
- `ACTIONS_RUNNER_HOOK_JOB_STARTED`
|
- `ACTIONS_RUNNER_HOOK_JOB_STARTED`
|
||||||
- `ACTIONS_RUNNER_HOOK_JOB_COMPLETED`
|
- `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.
|
We will execute `pwsh` (fallback to `powershell`) or `bash` (fallback to `sh`) as appropriate.
|
||||||
- `.sh` files will execute with the args `-e {pathtofile}`
|
- `.sh` files will execute with the args `-e {pathtofile}`
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Contributions
|
# Contributions
|
||||||
|
|
||||||
We welcome contributions in the form of issues and pull requests. We view the contributions and the process as the same for github and external contributors.Please note the runner typically requires changes across the entire system and we aim for issues in the runner to be entirely self contained and fixable here. Therefore, we will primarily handle bug issues opened in this repo and we kindly request you to create all feature and enhancement requests on the [GitHub Feedback](https://github.com/community/community/discussions/categories/actions-and-packages) page.
|
We welcome contributions in the form of issues and pull requests. We view the contributions and the process as the same for github and external contributors. Please note the runner typically requires changes across the entire system and we aim for issues in the runner to be entirely self contained and fixable here. Therefore, we will primarily handle bug issues opened in this repo and we kindly request you to create all feature and enhancement requests on the [GitHub Feedback](https://github.com/community/community/discussions/categories/actions-and-packages) page.
|
||||||
|
|
||||||
> IMPORTANT: Building your own runner is critical for the dev inner loop process when contributing changes. However, only runners built and distributed by GitHub (releases) are supported in production. Be aware that workflows and orchestrations run service side with the runner being a remote process to run steps. For that reason, the service can pull the runner forward so customizations can be lost.
|
> IMPORTANT: Building your own runner is critical for the dev inner loop process when contributing changes. However, only runners built and distributed by GitHub (releases) are supported in production. Be aware that workflows and orchestrations run service side with the runner being a remote process to run steps. For that reason, the service can pull the runner forward so customizations can be lost.
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# Source: https://github.com/dotnet/dotnet-docker
|
# Source: https://github.com/dotnet/dotnet-docker
|
||||||
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy as build
|
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy AS build
|
||||||
|
|
||||||
ARG TARGETOS
|
ARG TARGETOS
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG RUNNER_VERSION
|
ARG RUNNER_VERSION
|
||||||
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.6.1
|
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.7.0
|
||||||
ARG DOCKER_VERSION=27.3.1
|
ARG DOCKER_VERSION=28.0.1
|
||||||
ARG BUILDX_VERSION=0.18.0
|
ARG BUILDX_VERSION=0.21.2
|
||||||
|
|
||||||
RUN apt update -y && apt install curl unzip -y
|
RUN apt update -y && apt install curl unzip -y
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,36 @@
|
|||||||
## What's Changed
|
## What's Changed
|
||||||
|
* Bump docker/login-action from 2 to 3 by @dependabot in https://github.com/actions/runner/pull/3673
|
||||||
* Fix release workflow to use distinct artifact names by @ericsciple in https://github.com/actions/runner/pull/3485
|
* Bump actions/stale from 8 to 9 by @dependabot in https://github.com/actions/runner/pull/3554
|
||||||
* Update dotnet sdk to latest version @6.0.425 by @github-actions in https://github.com/actions/runner/pull/3433
|
* Bump docker/build-push-action from 3 to 6 by @dependabot in https://github.com/actions/runner/pull/3674
|
||||||
* add ref and type to job completion in run service by @yaananth in https://github.com/actions/runner/pull/3492
|
* update node version from 20.18.0 -> 20.18.2 by @aiqiaoy in https://github.com/actions/runner/pull/3682
|
||||||
* Remove Broker Migration Message logging by @luketomlinson in https://github.com/actions/runner/pull/3493
|
* Pass BillingOwnerId through Acquire/Complete calls by @luketomlinson in https://github.com/actions/runner/pull/3689
|
||||||
* Bump dotnet SDK to dotnet 8. by @TingluoHuang in https://github.com/actions/runner/pull/3500
|
* Do not retry CompleteJobAsync for known non-retryable errors by @ericsciple in https://github.com/actions/runner/pull/3696
|
||||||
* Remove dotnet8 compatibility test. by @TingluoHuang in https://github.com/actions/runner/pull/3502
|
* Update dotnet sdk to latest version @8.0.406 by @github-actions in https://github.com/actions/runner/pull/3712
|
||||||
* Remove node16 from the runner. by @TingluoHuang in https://github.com/actions/runner/pull/3503
|
* Update Dockerfile with new docker and buildx versions by @thboop in https://github.com/actions/runner/pull/3680
|
||||||
* send action name for run service by @yaananth in https://github.com/actions/runner/pull/3520
|
* chore: remove redundant words by @finaltrip in https://github.com/actions/runner/pull/3705
|
||||||
* Handle runner not found by @ericsciple in https://github.com/actions/runner/pull/3536
|
* fix: actions feedback link is incorrect by @Yaminyam in https://github.com/actions/runner/pull/3165
|
||||||
* Publish job telemetry to run-service. by @TingluoHuang in https://github.com/actions/runner/pull/3545
|
* Bump actions/github-script from 0.3.0 to 7.0.1 by @dependabot in https://github.com/actions/runner/pull/3557
|
||||||
* Fetch repo-level runner groups from API in v2 flow by @lucavallin in https://github.com/actions/runner/pull/3546
|
* Docker container provenance by @paveliak in https://github.com/actions/runner/pull/3736
|
||||||
* Allow runner to check service connection in background. by @TingluoHuang in https://github.com/actions/runner/pull/3542
|
* Add request-id to http eventsource trace. by @TingluoHuang in https://github.com/actions/runner/pull/3740
|
||||||
* Expose ENV for cache service v2. by @TingluoHuang in https://github.com/actions/runner/pull/3548
|
* Update Bocker and Buildx version to mitigate images scanners alerts by @Blizter in https://github.com/actions/runner/pull/3750
|
||||||
* Update runner docker image. by @TingluoHuang in https://github.com/actions/runner/pull/3511
|
* Fix typo, add invariant culture to timestamp for workflow log reporting by @GhadimiR in https://github.com/actions/runner/pull/3749
|
||||||
* Bump Azure.Storage.Blobs from 12.19.1 to 12.23.0 in /src by @dependabot in https://github.com/actions/runner/pull/3549
|
* Create vssconnection to actions service when URL provided. by @TingluoHuang in https://github.com/actions/runner/pull/3751
|
||||||
* fix dotnet-upgrade.yml to print right version by @TingluoHuang in https://github.com/actions/runner/pull/3550
|
* Housekeeping: Update npm packages and node version by @thboop in https://github.com/actions/runner/pull/3752
|
||||||
* Update dotnet sdk to latest version @8.0.404 by @github-actions in https://github.com/actions/runner/pull/3552
|
* Improve the out-of-date warning message. by @tecimovic in https://github.com/actions/runner/pull/3595
|
||||||
* Configure dependabot to check github-actions updates by @Goooler in https://github.com/actions/runner/pull/3333
|
* Update dotnet sdk to latest version @8.0.407 by @github-actions in https://github.com/actions/runner/pull/3753
|
||||||
* Bump actions/checkout from 3 to 4 by @dependabot in https://github.com/actions/runner/pull/3556
|
* Exit hosted runner cleanly during deprovisioning. by @TingluoHuang in https://github.com/actions/runner/pull/3755
|
||||||
|
* Send annotation title to run-service. by @TingluoHuang in https://github.com/actions/runner/pull/3757
|
||||||
|
* Allow server enforce runner settings. by @TingluoHuang in https://github.com/actions/runner/pull/3758
|
||||||
|
* Support refresh runner configs with pipelines service. by @TingluoHuang in https://github.com/actions/runner/pull/3706
|
||||||
|
|
||||||
## New Contributors
|
## New Contributors
|
||||||
* @lucavallin made their first contribution in https://github.com/actions/runner/pull/3546
|
* @finaltrip made their first contribution in https://github.com/actions/runner/pull/3705
|
||||||
* @Goooler made their first contribution in https://github.com/actions/runner/pull/3333
|
* @Yaminyam made their first contribution in https://github.com/actions/runner/pull/3165
|
||||||
|
* @Blizter made their first contribution in https://github.com/actions/runner/pull/3750
|
||||||
|
* @GhadimiR made their first contribution in https://github.com/actions/runner/pull/3749
|
||||||
|
* @tecimovic made their first contribution in https://github.com/actions/runner/pull/3595
|
||||||
|
|
||||||
**Full Changelog**: https://github.com/actions/runner/compare/v2.320.0...v2.321.0
|
**Full Changelog**: https://github.com/actions/runner/compare/v2.322.0...v2.323.0
|
||||||
|
|
||||||
_Note: Actions Runner follows a progressive release policy, so the latest release might not be available to your enterprise, organization, or repository yet.
|
_Note: Actions Runner follows a progressive release policy, so the latest release might not be available to your enterprise, organization, or repository yet.
|
||||||
To confirm which version of the Actions Runner you should expect, please view the download instructions for your enterprise, organization, or repository.
|
To confirm which version of the Actions Runner you should expect, please view the download instructions for your enterprise, organization, or repository.
|
||||||
|
|||||||
@@ -57,4 +57,13 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
</PropertyGroup>
|
</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>
|
</Project>
|
||||||
|
|||||||
1488
src/Misc/dotnet-install.ps1
vendored
1488
src/Misc/dotnet-install.ps1
vendored
File diff suppressed because it is too large
Load Diff
1256
src/Misc/dotnet-install.sh
vendored
1256
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",
|
"pack": "ncc build -o ../../layoutbin/hashFiles",
|
||||||
"all": "npm run format && npm run lint && npm run build && npm run pack",
|
"all": "npm run format && npm run lint && npm run build && npm run pack",
|
||||||
"prepare": "cd ../../../../ && husky install"
|
"prepare": "cd ../../../../ && husky install"
|
||||||
|
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -43,9 +42,9 @@
|
|||||||
"eslint": "^8.47.0",
|
"eslint": "^8.47.0",
|
||||||
"eslint-plugin-github": "^4.10.0",
|
"eslint-plugin-github": "^4.10.0",
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
"prettier": "^3.0.3",
|
|
||||||
"typescript": "^5.2.2",
|
|
||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"lint-staged": "^14.0.0"
|
"lint-staged": "^15.5.0",
|
||||||
|
"prettier": "^3.0.3",
|
||||||
|
"typescript": "^5.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ NODE_URL=https://nodejs.org/dist
|
|||||||
NODE_ALPINE_URL=https://github.com/actions/alpine_nodejs/releases/download
|
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.
|
# 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
|
# Follow the instructions here: https://github.com/actions/alpine_nodejs?tab=readme-ov-file#getting-started
|
||||||
NODE20_VERSION="20.18.0"
|
NODE20_VERSION="20.19.0"
|
||||||
|
|
||||||
get_abs_path() {
|
get_abs_path() {
|
||||||
# exploits the fact that pwd will print abs path when no args
|
# exploits the fact that pwd will print abs path when no args
|
||||||
|
|||||||
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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
public async Task ConnectAsync(Uri serverUri, VssCredentials credentials)
|
public async Task ConnectAsync(Uri serverUri, VssCredentials credentials)
|
||||||
{
|
{
|
||||||
|
Trace.Entering();
|
||||||
_brokerUri = serverUri;
|
_brokerUri = serverUri;
|
||||||
|
|
||||||
_connection = VssUtil.CreateRawConnection(serverUri, credentials);
|
_connection = VssUtil.CreateRawConnection(serverUri, credentials);
|
||||||
@@ -88,12 +89,17 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
public Task ForceRefreshConnection(VssCredentials credentials)
|
public Task ForceRefreshConnection(VssCredentials credentials)
|
||||||
{
|
{
|
||||||
return ConnectAsync(_brokerUri, credentials);
|
if (!string.IsNullOrEmpty(_brokerUri?.AbsoluteUri))
|
||||||
|
{
|
||||||
|
return ConnectAsync(_brokerUri, credentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ShouldRetryException(Exception ex)
|
public bool ShouldRetryException(Exception ex)
|
||||||
{
|
{
|
||||||
if (ex is AccessDeniedException || ex is RunnerNotFoundException)
|
if (ex is AccessDeniedException || ex is RunnerNotFoundException || ex is HostedRunnerDeprovisionedException)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,8 +119,11 @@ namespace GitHub.Runner.Common
|
|||||||
CredentialData GetCredentials();
|
CredentialData GetCredentials();
|
||||||
CredentialData GetMigratedCredentials();
|
CredentialData GetMigratedCredentials();
|
||||||
RunnerSettings GetSettings();
|
RunnerSettings GetSettings();
|
||||||
|
RunnerSettings GetMigratedSettings();
|
||||||
void SaveCredential(CredentialData credential);
|
void SaveCredential(CredentialData credential);
|
||||||
|
void SaveMigratedCredential(CredentialData credential);
|
||||||
void SaveSettings(RunnerSettings settings);
|
void SaveSettings(RunnerSettings settings);
|
||||||
|
void SaveMigratedSettings(RunnerSettings settings);
|
||||||
void DeleteCredential();
|
void DeleteCredential();
|
||||||
void DeleteMigratedCredential();
|
void DeleteMigratedCredential();
|
||||||
void DeleteSettings();
|
void DeleteSettings();
|
||||||
@@ -130,6 +133,7 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
private string _binPath;
|
private string _binPath;
|
||||||
private string _configFilePath;
|
private string _configFilePath;
|
||||||
|
private string _migratedConfigFilePath;
|
||||||
private string _credFilePath;
|
private string _credFilePath;
|
||||||
private string _migratedCredFilePath;
|
private string _migratedCredFilePath;
|
||||||
private string _serviceConfigFilePath;
|
private string _serviceConfigFilePath;
|
||||||
@@ -137,6 +141,7 @@ namespace GitHub.Runner.Common
|
|||||||
private CredentialData _creds;
|
private CredentialData _creds;
|
||||||
private CredentialData _migratedCreds;
|
private CredentialData _migratedCreds;
|
||||||
private RunnerSettings _settings;
|
private RunnerSettings _settings;
|
||||||
|
private RunnerSettings _migratedSettings;
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
public override void Initialize(IHostContext hostContext)
|
||||||
{
|
{
|
||||||
@@ -154,6 +159,9 @@ namespace GitHub.Runner.Common
|
|||||||
_configFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Runner);
|
_configFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Runner);
|
||||||
Trace.Info("ConfigFilePath: {0}", _configFilePath);
|
Trace.Info("ConfigFilePath: {0}", _configFilePath);
|
||||||
|
|
||||||
|
_migratedConfigFilePath = hostContext.GetConfigFile(WellKnownConfigFile.MigratedRunner);
|
||||||
|
Trace.Info("MigratedConfigFilePath: {0}", _migratedConfigFilePath);
|
||||||
|
|
||||||
_credFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Credentials);
|
_credFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Credentials);
|
||||||
Trace.Info("CredFilePath: {0}", _credFilePath);
|
Trace.Info("CredFilePath: {0}", _credFilePath);
|
||||||
|
|
||||||
@@ -169,7 +177,7 @@ namespace GitHub.Runner.Common
|
|||||||
public bool HasCredentials()
|
public bool HasCredentials()
|
||||||
{
|
{
|
||||||
Trace.Info("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);
|
Trace.Info("stored {0}", credsStored);
|
||||||
return credsStored;
|
return credsStored;
|
||||||
}
|
}
|
||||||
@@ -177,7 +185,7 @@ namespace GitHub.Runner.Common
|
|||||||
public bool IsConfigured()
|
public bool IsConfigured()
|
||||||
{
|
{
|
||||||
Trace.Info("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);
|
Trace.Info("IsConfigured: {0}", configured);
|
||||||
return configured;
|
return configured;
|
||||||
}
|
}
|
||||||
@@ -185,7 +193,7 @@ namespace GitHub.Runner.Common
|
|||||||
public bool IsServiceConfigured()
|
public bool IsServiceConfigured()
|
||||||
{
|
{
|
||||||
Trace.Info("IsServiceConfigured()");
|
Trace.Info("IsServiceConfigured()");
|
||||||
bool serviceConfigured = (new FileInfo(_serviceConfigFilePath)).Exists;
|
bool serviceConfigured = new FileInfo(_serviceConfigFilePath).Exists;
|
||||||
Trace.Info($"IsServiceConfigured: {serviceConfigured}");
|
Trace.Info($"IsServiceConfigured: {serviceConfigured}");
|
||||||
return serviceConfigured;
|
return serviceConfigured;
|
||||||
}
|
}
|
||||||
@@ -229,6 +237,25 @@ namespace GitHub.Runner.Common
|
|||||||
return _settings;
|
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)
|
public void SaveCredential(CredentialData credential)
|
||||||
{
|
{
|
||||||
Trace.Info("Saving {0} credential @ {1}", credential.Scheme, _credFilePath);
|
Trace.Info("Saving {0} credential @ {1}", credential.Scheme, _credFilePath);
|
||||||
@@ -244,6 +271,21 @@ namespace GitHub.Runner.Common
|
|||||||
File.SetAttributes(_credFilePath, File.GetAttributes(_credFilePath) | FileAttributes.Hidden);
|
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)
|
public void SaveSettings(RunnerSettings settings)
|
||||||
{
|
{
|
||||||
Trace.Info("Saving runner settings.");
|
Trace.Info("Saving runner settings.");
|
||||||
@@ -259,6 +301,21 @@ namespace GitHub.Runner.Common
|
|||||||
File.SetAttributes(_configFilePath, File.GetAttributes(_configFilePath) | FileAttributes.Hidden);
|
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()
|
public void DeleteCredential()
|
||||||
{
|
{
|
||||||
IOUtil.Delete(_credFilePath, default(CancellationToken));
|
IOUtil.Delete(_credFilePath, default(CancellationToken));
|
||||||
@@ -273,6 +330,12 @@ namespace GitHub.Runner.Common
|
|||||||
public void DeleteSettings()
|
public void DeleteSettings()
|
||||||
{
|
{
|
||||||
IOUtil.Delete(_configFilePath, default(CancellationToken));
|
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
|
public enum WellKnownConfigFile
|
||||||
{
|
{
|
||||||
Runner,
|
Runner,
|
||||||
|
MigratedRunner,
|
||||||
Credentials,
|
Credentials,
|
||||||
MigratedCredentials,
|
MigratedCredentials,
|
||||||
RSACredentials,
|
RSACredentials,
|
||||||
@@ -256,6 +257,7 @@ namespace GitHub.Runner.Common
|
|||||||
public static readonly string ForcedActionsNodeVersion = "ACTIONS_RUNNER_FORCE_ACTIONS_NODE_VERSION";
|
public static readonly string ForcedActionsNodeVersion = "ACTIONS_RUNNER_FORCE_ACTIONS_NODE_VERSION";
|
||||||
public static readonly string PrintLogToStdout = "ACTIONS_RUNNER_PRINT_LOG_TO_STDOUT";
|
public static readonly string PrintLogToStdout = "ACTIONS_RUNNER_PRINT_LOG_TO_STDOUT";
|
||||||
public static readonly string ActionArchiveCacheDirectory = "ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE";
|
public static readonly string ActionArchiveCacheDirectory = "ACTIONS_RUNNER_ACTION_ARCHIVE_CACHE";
|
||||||
|
public static readonly string ActionsTerminationGracePeriodSeconds = "ACTIONS_RUNNER_TERMINATION_GRACE_PERIOD_SECONDS";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class System
|
public static class System
|
||||||
|
|||||||
@@ -34,9 +34,14 @@ namespace GitHub.Runner.Common
|
|||||||
T GetService<T>() where T : class, IRunnerService;
|
T GetService<T>() where T : class, IRunnerService;
|
||||||
void SetDefaultCulture(string name);
|
void SetDefaultCulture(string name);
|
||||||
event EventHandler Unloading;
|
event EventHandler Unloading;
|
||||||
void ShutdownRunner(ShutdownReason reason);
|
void ShutdownRunner(ShutdownReason reason, TimeSpan delay = default);
|
||||||
void WritePerfCounter(string counter);
|
void WritePerfCounter(string counter);
|
||||||
void LoadDefaultUserAgents();
|
void LoadDefaultUserAgents();
|
||||||
|
|
||||||
|
bool AllowAuthMigration { get; }
|
||||||
|
void EnableAuthMigration(string trace);
|
||||||
|
void DeferAuthMigration(TimeSpan deferred, string trace);
|
||||||
|
event EventHandler<AuthMigrationEventArgs> AuthMigrationChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum StartupType
|
public enum StartupType
|
||||||
@@ -69,13 +74,24 @@ namespace GitHub.Runner.Common
|
|||||||
private string _perfFile;
|
private string _perfFile;
|
||||||
private RunnerWebProxy _webProxy = new();
|
private RunnerWebProxy _webProxy = new();
|
||||||
private string _hostType = string.Empty;
|
private string _hostType = string.Empty;
|
||||||
|
private ShutdownReason _shutdownReason = ShutdownReason.UserCancelled;
|
||||||
|
private int _shutdownReasonSet = 0;
|
||||||
|
|
||||||
|
// 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 Unloading;
|
||||||
|
public event EventHandler<AuthMigrationEventArgs> AuthMigrationChanged;
|
||||||
public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token;
|
public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token;
|
||||||
public ShutdownReason RunnerShutdownReason { get; private set; }
|
public ShutdownReason RunnerShutdownReason => _shutdownReason;
|
||||||
public ISecretMasker SecretMasker => _secretMasker;
|
public ISecretMasker SecretMasker => _secretMasker;
|
||||||
public List<ProductInfoHeaderValue> UserAgents => _userAgents;
|
public List<ProductInfoHeaderValue> UserAgents => _userAgents;
|
||||||
public RunnerWebProxy WebProxy => _webProxy;
|
public RunnerWebProxy WebProxy => _webProxy;
|
||||||
|
public bool AllowAuthMigration => _allowAuthMigration.IsSet;
|
||||||
public HostContext(string hostType, string logFile = null)
|
public HostContext(string hostType, string logFile = null)
|
||||||
{
|
{
|
||||||
// Validate args.
|
// Validate args.
|
||||||
@@ -207,6 +223,71 @@ namespace GitHub.Runner.Common
|
|||||||
LoadDefaultUserAgents();
|
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()
|
public void LoadDefaultUserAgents()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(WebProxy.HttpProxyAddress) && string.IsNullOrEmpty(WebProxy.HttpsProxyAddress))
|
if (string.IsNullOrEmpty(WebProxy.HttpProxyAddress) && string.IsNullOrEmpty(WebProxy.HttpsProxyAddress))
|
||||||
@@ -343,6 +424,12 @@ namespace GitHub.Runner.Common
|
|||||||
".runner");
|
".runner");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case WellKnownConfigFile.MigratedRunner:
|
||||||
|
path = Path.Combine(
|
||||||
|
GetDirectory(WellKnownDirectory.Root),
|
||||||
|
".runner_migrated");
|
||||||
|
break;
|
||||||
|
|
||||||
case WellKnownConfigFile.Credentials:
|
case WellKnownConfigFile.Credentials:
|
||||||
path = Path.Combine(
|
path = Path.Combine(
|
||||||
GetDirectory(WellKnownDirectory.Root),
|
GetDirectory(WellKnownDirectory.Root),
|
||||||
@@ -488,12 +575,28 @@ namespace GitHub.Runner.Common
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void ShutdownRunner(ShutdownReason reason)
|
public void ShutdownRunner(ShutdownReason reason, TimeSpan delay = default)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(reason, nameof(reason));
|
ArgUtil.NotNull(reason, nameof(reason));
|
||||||
_trace.Info($"Runner will be shutdown for {reason.ToString()}");
|
_trace.Info($"Runner will be shutdown for {reason.ToString()} after {delay.TotalSeconds} seconds.");
|
||||||
RunnerShutdownReason = reason;
|
if (Interlocked.CompareExchange(ref _shutdownReasonSet, 1, 0) == 0)
|
||||||
_runnerShutdownTokenSource.Cancel();
|
{
|
||||||
|
// Set the shutdown reason only if it hasn't been set before.
|
||||||
|
_shutdownReason = reason;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_trace.Verbose($"Runner shutdown reason already set to {_shutdownReason.ToString()}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delay.TotalSeconds == 0)
|
||||||
|
{
|
||||||
|
_runnerShutdownTokenSource.Cancel();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_runnerShutdownTokenSource.CancelAfter(delay);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
@@ -543,6 +646,18 @@ namespace GitHub.Runner.Common
|
|||||||
_loadContext.Unloading -= LoadContext_Unloading;
|
_loadContext.Unloading -= LoadContext_Unloading;
|
||||||
_loadContext = null;
|
_loadContext = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_authMigrationAutoReenableTask != null)
|
||||||
|
{
|
||||||
|
_authMigrationAutoReenableTaskCancellationTokenSource?.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_authMigrationAutoReenableTaskCancellationTokenSource != null)
|
||||||
|
{
|
||||||
|
_authMigrationAutoReenableTaskCancellationTokenSource?.Dispose();
|
||||||
|
_authMigrationAutoReenableTaskCancellationTokenSource = null;
|
||||||
|
}
|
||||||
|
|
||||||
_httpTraceSubscription?.Dispose();
|
_httpTraceSubscription?.Dispose();
|
||||||
_diagListenerSubscription?.Dispose();
|
_diagListenerSubscription?.Dispose();
|
||||||
_traceManager?.Dispose();
|
_traceManager?.Dispose();
|
||||||
@@ -629,7 +744,7 @@ namespace GitHub.Runner.Common
|
|||||||
payload[0] = Enum.Parse(typeof(GitHub.Services.Common.VssCredentialsType), ((int)payload[0]).ToString());
|
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);
|
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 ConnectAsync(Uri serverUrl, VssCredentials credentials);
|
||||||
|
|
||||||
Task<AgentJobRequestMessage> GetJobMessageAsync(string id, CancellationToken token);
|
Task<AgentJobRequestMessage> GetJobMessageAsync(string id, string billingOwnerId, CancellationToken token);
|
||||||
|
|
||||||
Task CompleteJobAsync(
|
Task CompleteJobAsync(
|
||||||
Guid planId,
|
Guid planId,
|
||||||
@@ -29,6 +29,7 @@ namespace GitHub.Runner.Common
|
|||||||
IList<Annotation> jobAnnotations,
|
IList<Annotation> jobAnnotations,
|
||||||
string environmentUrl,
|
string environmentUrl,
|
||||||
IList<Telemetry> telemetry,
|
IList<Telemetry> telemetry,
|
||||||
|
string billingOwnerId,
|
||||||
CancellationToken token);
|
CancellationToken token);
|
||||||
|
|
||||||
Task<RenewJobResponse> RenewJobAsync(Guid planId, Guid jobId, CancellationToken token);
|
Task<RenewJobResponse> RenewJobAsync(Guid planId, Guid jobId, CancellationToken token);
|
||||||
@@ -58,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();
|
CheckConnection();
|
||||||
return RetryRequest<AgentJobRequestMessage>(
|
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 =>
|
shouldRetry: ex =>
|
||||||
ex is not TaskOrchestrationJobNotFoundException && // HTTP status 404
|
ex is not TaskOrchestrationJobNotFoundException && // HTTP status 404
|
||||||
ex is not TaskOrchestrationJobAlreadyAcquiredException && // HTTP status 409
|
ex is not TaskOrchestrationJobAlreadyAcquiredException && // HTTP status 409
|
||||||
@@ -78,11 +79,15 @@ namespace GitHub.Runner.Common
|
|||||||
IList<Annotation> jobAnnotations,
|
IList<Annotation> jobAnnotations,
|
||||||
string environmentUrl,
|
string environmentUrl,
|
||||||
IList<Telemetry> telemetry,
|
IList<Telemetry> telemetry,
|
||||||
|
string billingOwnerId,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
CheckConnection();
|
CheckConnection();
|
||||||
return RetryRequest(
|
return RetryRequest(
|
||||||
async () => await _runServiceHttpClient.CompleteJobAsync(requestUri, planId, jobId, result, outputs, stepResults, jobAnnotations, environmentUrl, telemetry, 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)
|
public Task<RenewJobResponse> RenewJobAsync(Guid planId, Guid jobId, CancellationToken cancellationToken)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
using GitHub.DistributedTask.WebApi;
|
using System;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.Services.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Services.Common;
|
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
|
using GitHub.Services.Common;
|
||||||
|
using GitHub.Services.WebApi;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common
|
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);
|
Task<PackageMetadata> GetPackageAsync(string packageType, string platform, string version, bool includeToken, CancellationToken cancellationToken);
|
||||||
|
|
||||||
// agent update
|
// 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
|
public sealed class RunnerServer : RunnerService, IRunnerServer
|
||||||
@@ -315,10 +318,17 @@ namespace GitHub.Runner.Common
|
|||||||
return _genericTaskAgentClient.GetPackageAsync(packageType, platform, version, includeToken, cancellationToken: cancellationToken);
|
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);
|
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,
|
protected async Task RetryRequest(Func<Task> func,
|
||||||
CancellationToken cancellationToken,
|
CancellationToken cancellationToken,
|
||||||
int maxRetryAttemptsCount = 5
|
int maxRetryAttemptsCount = 5,
|
||||||
|
Func<Exception, bool> shouldRetry = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
async Task<Unit> wrappedFunc()
|
async Task<Unit> wrappedFunc()
|
||||||
@@ -78,7 +79,7 @@ namespace GitHub.Runner.Common
|
|||||||
await func();
|
await func();
|
||||||
return Unit.Value;
|
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,
|
protected async Task<T> RetryRequest<T>(Func<Task<T>> func,
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Common.Util;
|
||||||
using GitHub.Runner.Listener.Configuration;
|
using GitHub.Runner.Listener.Configuration;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
using GitHub.Runner.Common.Util;
|
|
||||||
using GitHub.Services.Common;
|
using GitHub.Services.Common;
|
||||||
using GitHub.Services.OAuth;
|
using GitHub.Services.OAuth;
|
||||||
using GitHub.Services.WebApi;
|
using GitHub.Services.WebApi;
|
||||||
@@ -26,21 +26,27 @@ namespace GitHub.Runner.Listener
|
|||||||
private TaskAgentStatus runnerStatus = TaskAgentStatus.Online;
|
private TaskAgentStatus runnerStatus = TaskAgentStatus.Online;
|
||||||
private CancellationTokenSource _getMessagesTokenSource;
|
private CancellationTokenSource _getMessagesTokenSource;
|
||||||
private VssCredentials _creds;
|
private VssCredentials _creds;
|
||||||
|
private VssCredentials _credsV2;
|
||||||
private TaskAgentSession _session;
|
private TaskAgentSession _session;
|
||||||
|
private IRunnerServer _runnerServer;
|
||||||
private IBrokerServer _brokerServer;
|
private IBrokerServer _brokerServer;
|
||||||
|
private ICredentialManager _credMgr;
|
||||||
private readonly Dictionary<string, int> _sessionCreationExceptionTracker = new();
|
private readonly Dictionary<string, int> _sessionCreationExceptionTracker = new();
|
||||||
private bool _accessTokenRevoked = false;
|
private bool _accessTokenRevoked = false;
|
||||||
private readonly TimeSpan _sessionCreationRetryInterval = TimeSpan.FromSeconds(30);
|
private readonly TimeSpan _sessionCreationRetryInterval = TimeSpan.FromSeconds(30);
|
||||||
private readonly TimeSpan _sessionConflictRetryLimit = TimeSpan.FromMinutes(4);
|
private readonly TimeSpan _sessionConflictRetryLimit = TimeSpan.FromMinutes(4);
|
||||||
private readonly TimeSpan _clockSkewRetryLimit = TimeSpan.FromMinutes(30);
|
private readonly TimeSpan _clockSkewRetryLimit = TimeSpan.FromMinutes(30);
|
||||||
|
private bool _needRefreshCredsV2 = false;
|
||||||
|
private bool _handlerInitialized = false;
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
public override void Initialize(IHostContext hostContext)
|
||||||
{
|
{
|
||||||
base.Initialize(hostContext);
|
base.Initialize(hostContext);
|
||||||
|
|
||||||
_term = HostContext.GetService<ITerminal>();
|
_term = HostContext.GetService<ITerminal>();
|
||||||
|
_runnerServer = HostContext.GetService<IRunnerServer>();
|
||||||
_brokerServer = HostContext.GetService<IBrokerServer>();
|
_brokerServer = HostContext.GetService<IBrokerServer>();
|
||||||
|
_credMgr = HostContext.GetService<ICredentialManager>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CreateSessionResult> CreateSessionAsync(CancellationToken token)
|
public async Task<CreateSessionResult> CreateSessionAsync(CancellationToken token)
|
||||||
@@ -50,7 +56,8 @@ namespace GitHub.Runner.Listener
|
|||||||
// Settings
|
// Settings
|
||||||
var configManager = HostContext.GetService<IConfigurationManager>();
|
var configManager = HostContext.GetService<IConfigurationManager>();
|
||||||
_settings = configManager.LoadSettings();
|
_settings = configManager.LoadSettings();
|
||||||
var serverUrl = _settings.ServerUrlV2;
|
var serverUrlV2 = _settings.ServerUrlV2;
|
||||||
|
var serverUrl = _settings.ServerUrl;
|
||||||
Trace.Info(_settings);
|
Trace.Info(_settings);
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(_settings.ServerUrlV2))
|
if (string.IsNullOrEmpty(_settings.ServerUrlV2))
|
||||||
@@ -60,8 +67,7 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
// Create connection.
|
// Create connection.
|
||||||
Trace.Info("Loading Credentials");
|
Trace.Info("Loading Credentials");
|
||||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
_creds = _credMgr.LoadCredentials(allowAuthUrlV2: false);
|
||||||
_creds = credMgr.LoadCredentials();
|
|
||||||
|
|
||||||
var agent = new TaskAgentReference
|
var agent = new TaskAgentReference
|
||||||
{
|
{
|
||||||
@@ -84,9 +90,18 @@ namespace GitHub.Runner.Listener
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Trace.Info("Connecting to the Broker Server...");
|
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");
|
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.WriteLine();
|
||||||
_term.WriteSuccessMessage("Connected to GitHub");
|
_term.WriteSuccessMessage("Connected to GitHub");
|
||||||
_term.WriteLine();
|
_term.WriteLine();
|
||||||
@@ -101,6 +116,13 @@ namespace GitHub.Runner.Listener
|
|||||||
encounteringError = false;
|
encounteringError = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_handlerInitialized)
|
||||||
|
{
|
||||||
|
// Register event handler for auth migration state change
|
||||||
|
HostContext.AuthMigrationChanged += HandleAuthMigrationChanged;
|
||||||
|
_handlerInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
return CreateSessionResult.Success;
|
return CreateSessionResult.Success;
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||||
@@ -119,7 +141,7 @@ namespace GitHub.Runner.Listener
|
|||||||
Trace.Error("Catch exception during create session.");
|
Trace.Error("Catch exception during create session.");
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
|
|
||||||
if (ex is VssOAuthTokenRequestException vssOAuthEx && _creds.Federated is VssOAuthCredential vssOAuthCred)
|
if (ex is VssOAuthTokenRequestException vssOAuthEx && _credsV2.Federated is VssOAuthCredential vssOAuthCred)
|
||||||
{
|
{
|
||||||
// "invalid_client" means the runner registration has been deleted from the server.
|
// "invalid_client" means the runner registration has been deleted from the server.
|
||||||
if (string.Equals(vssOAuthEx.Error, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(vssOAuthEx.Error, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
||||||
@@ -131,7 +153,7 @@ namespace GitHub.Runner.Listener
|
|||||||
// Check whether we get 401 because the runner registration already removed by the service.
|
// 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.
|
// If the runner registration get deleted, we can't exchange oauth token.
|
||||||
Trace.Error("Test oauth app registration.");
|
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);
|
var authError = await oauthTokenProvider.ValidateCredentialAsync(token);
|
||||||
if (string.Equals(authError, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(authError, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
@@ -150,6 +172,12 @@ namespace GitHub.Runner.Listener
|
|||||||
return CreateSessionResult.Failure;
|
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
|
if (!encounteringError) //print the message only on the first error
|
||||||
{
|
{
|
||||||
_term.WriteError($"{DateTime.UtcNow:u}: Runner connect error: {ex.Message}. Retrying until reconnected.");
|
_term.WriteError($"{DateTime.UtcNow:u}: Runner connect error: {ex.Message}. Retrying until reconnected.");
|
||||||
@@ -166,6 +194,11 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
if (_session != null && _session.SessionId != Guid.Empty)
|
if (_session != null && _session.SessionId != Guid.Empty)
|
||||||
{
|
{
|
||||||
|
if (_handlerInitialized)
|
||||||
|
{
|
||||||
|
HostContext.AuthMigrationChanged -= HandleAuthMigrationChanged;
|
||||||
|
}
|
||||||
|
|
||||||
if (!_accessTokenRevoked)
|
if (!_accessTokenRevoked)
|
||||||
{
|
{
|
||||||
using (var ts = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
|
using (var ts = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
|
||||||
@@ -208,6 +241,13 @@ namespace GitHub.Runner.Listener
|
|||||||
_getMessagesTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
|
_getMessagesTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (_needRefreshCredsV2)
|
||||||
|
{
|
||||||
|
Trace.Info("Refreshing broker connection.");
|
||||||
|
await RefreshBrokerConnectionAsync();
|
||||||
|
_needRefreshCredsV2 = false;
|
||||||
|
}
|
||||||
|
|
||||||
message = await _brokerServer.GetRunnerMessageAsync(_session.SessionId,
|
message = await _brokerServer.GetRunnerMessageAsync(_session.SessionId,
|
||||||
runnerStatus,
|
runnerStatus,
|
||||||
BuildConstants.RunnerPackage.Version,
|
BuildConstants.RunnerPackage.Version,
|
||||||
@@ -238,6 +278,11 @@ namespace GitHub.Runner.Listener
|
|||||||
Trace.Info("Runner OAuth token has been revoked. Unable to pull message.");
|
Trace.Info("Runner OAuth token has been revoked. Unable to pull message.");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
catch (HostedRunnerDeprovisionedException)
|
||||||
|
{
|
||||||
|
Trace.Info("Hosted runner has been deprovisioned.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
catch (AccessDeniedException e) when (e.ErrorCode == 1)
|
catch (AccessDeniedException e) when (e.ErrorCode == 1)
|
||||||
{
|
{
|
||||||
throw;
|
throw;
|
||||||
@@ -282,6 +327,12 @@ namespace GitHub.Runner.Listener
|
|||||||
encounteringError = true;
|
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
|
// re-create VssConnection before next retry
|
||||||
await RefreshBrokerConnectionAsync();
|
await RefreshBrokerConnectionAsync();
|
||||||
|
|
||||||
@@ -313,7 +364,7 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RefreshListenerTokenAsync(CancellationToken cancellationToken)
|
public async Task RefreshListenerTokenAsync()
|
||||||
{
|
{
|
||||||
await RefreshBrokerConnectionAsync();
|
await RefreshBrokerConnectionAsync();
|
||||||
}
|
}
|
||||||
@@ -416,17 +467,16 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
private async Task RefreshBrokerConnectionAsync()
|
private async Task RefreshBrokerConnectionAsync()
|
||||||
{
|
{
|
||||||
var configManager = HostContext.GetService<IConfigurationManager>();
|
Trace.Info("Reload credentials.");
|
||||||
_settings = configManager.LoadSettings();
|
_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");
|
Trace.Info($"Auth migration changed. Current allow auth migration state: {HostContext.AllowAuthMigration}");
|
||||||
}
|
_needRefreshCredsV2 = true;
|
||||||
|
|
||||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
|
||||||
VssCredentials creds = credMgr.LoadCredentials();
|
|
||||||
await _brokerServer.ConnectAsync(new Uri(_settings.ServerUrlV2), creds);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
runnerSettings.ServerUrl = inputUrl;
|
runnerSettings.ServerUrl = inputUrl;
|
||||||
// Get the credentials
|
// Get the credentials
|
||||||
credProvider = GetCredentialProvider(command, runnerSettings.ServerUrl);
|
credProvider = GetCredentialProvider(command, runnerSettings.ServerUrl);
|
||||||
creds = credProvider.GetVssCredentials(HostContext);
|
creds = credProvider.GetVssCredentials(HostContext, allowAuthUrlV2: false);
|
||||||
Trace.Info("legacy vss cred retrieved");
|
Trace.Info("legacy vss cred retrieved");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -366,7 +366,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
{
|
{
|
||||||
{ "clientId", agent.Authorization.ClientId.ToString("D") },
|
{ "clientId", agent.Authorization.ClientId.ToString("D") },
|
||||||
{ "authorizationUrl", agent.Authorization.AuthorizationUrl.AbsoluteUri },
|
{ "authorizationUrl", agent.Authorization.AuthorizationUrl.AbsoluteUri },
|
||||||
{ "requireFipsCryptography", agent.Properties.GetValue("RequireFipsCryptography", false).ToString() }
|
{ "requireFipsCryptography", agent.Properties.GetValue("RequireFipsCryptography", true).ToString() }
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -384,7 +384,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
if (!runnerSettings.UseV2Flow)
|
if (!runnerSettings.UseV2Flow)
|
||||||
{
|
{
|
||||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
var credMgr = HostContext.GetService<ICredentialManager>();
|
||||||
VssCredentials credential = credMgr.LoadCredentials();
|
VssCredentials credential = credMgr.LoadCredentials(allowAuthUrlV2: false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), credential);
|
await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), credential);
|
||||||
@@ -404,6 +404,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");
|
_term.WriteSection("Runner settings");
|
||||||
|
|
||||||
// We will Combine() what's stored with root. Defaults to string a relative path
|
// We will Combine() what's stored with root. Defaults to string a relative path
|
||||||
@@ -505,7 +519,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
if (string.IsNullOrEmpty(settings.GitHubUrl))
|
if (string.IsNullOrEmpty(settings.GitHubUrl))
|
||||||
{
|
{
|
||||||
var credProvider = GetCredentialProvider(command, settings.ServerUrl);
|
var credProvider = GetCredentialProvider(command, settings.ServerUrl);
|
||||||
creds = credProvider.GetVssCredentials(HostContext);
|
creds = credProvider.GetVssCredentials(HostContext, allowAuthUrlV2: false);
|
||||||
Trace.Info("legacy vss cred retrieved");
|
Trace.Info("legacy vss cred retrieved");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
public interface ICredentialManager : IRunnerService
|
public interface ICredentialManager : IRunnerService
|
||||||
{
|
{
|
||||||
ICredentialProvider GetCredentialProvider(string credType);
|
ICredentialProvider GetCredentialProvider(string credType);
|
||||||
VssCredentials LoadCredentials();
|
VssCredentials LoadCredentials(bool allowAuthUrlV2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CredentialManager : RunnerService, ICredentialManager
|
public class CredentialManager : RunnerService, ICredentialManager
|
||||||
@@ -40,7 +40,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
return creds;
|
return creds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public VssCredentials LoadCredentials()
|
public VssCredentials LoadCredentials(bool allowAuthUrlV2)
|
||||||
{
|
{
|
||||||
IConfigurationStore store = HostContext.GetService<IConfigurationStore>();
|
IConfigurationStore store = HostContext.GetService<IConfigurationStore>();
|
||||||
|
|
||||||
@@ -51,21 +51,16 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
|
|
||||||
CredentialData credData = store.GetCredentials();
|
CredentialData credData = store.GetCredentials();
|
||||||
var migratedCred = store.GetMigratedCredentials();
|
var migratedCred = store.GetMigratedCredentials();
|
||||||
if (migratedCred != null)
|
if (migratedCred != null &&
|
||||||
|
migratedCred.Scheme == Constants.Configuration.OAuth)
|
||||||
{
|
{
|
||||||
credData = migratedCred;
|
credData = migratedCred;
|
||||||
|
|
||||||
// Re-write .credentials with Token URL
|
|
||||||
store.SaveCredential(credData);
|
|
||||||
|
|
||||||
// Delete .credentials_migrated
|
|
||||||
store.DeleteMigratedCredential();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ICredentialProvider credProv = GetCredentialProvider(credData.Scheme);
|
ICredentialProvider credProv = GetCredentialProvider(credData.Scheme);
|
||||||
credProv.CredentialData = credData;
|
credProv.CredentialData = credData;
|
||||||
|
|
||||||
VssCredentials creds = credProv.GetVssCredentials(HostContext);
|
VssCredentials creds = credProv.GetVssCredentials(HostContext, allowAuthUrlV2);
|
||||||
|
|
||||||
return creds;
|
return creds;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using GitHub.Services.Common;
|
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
|
using GitHub.Services.Common;
|
||||||
using GitHub.Services.OAuth;
|
using GitHub.Services.OAuth;
|
||||||
|
|
||||||
namespace GitHub.Runner.Listener.Configuration
|
namespace GitHub.Runner.Listener.Configuration
|
||||||
@@ -10,7 +10,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
{
|
{
|
||||||
Boolean RequireInteractive { get; }
|
Boolean RequireInteractive { get; }
|
||||||
CredentialData CredentialData { get; set; }
|
CredentialData CredentialData { get; set; }
|
||||||
VssCredentials GetVssCredentials(IHostContext context);
|
VssCredentials GetVssCredentials(IHostContext context, bool allowAuthUrlV2);
|
||||||
void EnsureCredential(IHostContext context, CommandSettings command, string serverUrl);
|
void EnsureCredential(IHostContext context, CommandSettings command, string serverUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
public virtual Boolean RequireInteractive => false;
|
public virtual Boolean RequireInteractive => false;
|
||||||
public CredentialData CredentialData { get; set; }
|
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);
|
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 OAuthAccessTokenCredential() : base(Constants.Configuration.OAuthAccessToken) { }
|
||||||
|
|
||||||
public override VssCredentials GetVssCredentials(IHostContext context)
|
public override VssCredentials GetVssCredentials(IHostContext context, bool allowAuthUrlV2)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(context, nameof(context));
|
ArgUtil.NotNull(context, nameof(context));
|
||||||
Tracing trace = context.GetTrace(nameof(OAuthAccessTokenCredential));
|
Tracing trace = context.GetTrace(nameof(OAuthAccessTokenCredential));
|
||||||
|
|||||||
@@ -22,10 +22,18 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
// Nothing to verify here
|
// 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 clientId = this.CredentialData.Data.GetValueOrDefault("clientId", null);
|
||||||
var authorizationUrl = this.CredentialData.Data.GetValueOrDefault("authorizationUrl", 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
|
// For back compat with .credential file that doesn't has 'oauthEndpointUrl' section
|
||||||
var oauthEndpointUrl = this.CredentialData.Data.GetValueOrDefault("oauthEndpointUrl", authorizationUrl);
|
var oauthEndpointUrl = this.CredentialData.Data.GetValueOrDefault("oauthEndpointUrl", authorizationUrl);
|
||||||
|
|||||||
@@ -545,28 +545,36 @@ namespace GitHub.Runner.Listener
|
|||||||
detailInfo = string.Join(Environment.NewLine, workerOutput);
|
detailInfo = string.Join(Environment.NewLine, workerOutput);
|
||||||
Trace.Info($"Return code {returnCode} indicate worker encounter an unhandled exception or app crash, attach worker stdout/stderr to JobRequest result.");
|
Trace.Info($"Return code {returnCode} indicate worker encounter an unhandled exception or app crash, attach worker stdout/stderr to JobRequest result.");
|
||||||
|
|
||||||
var jobServer = await InitializeJobServerAsync(systemConnection);
|
try
|
||||||
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = detailInfo };
|
|
||||||
unhandledExceptionIssue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.WorkerCrash;
|
|
||||||
switch (jobServer)
|
|
||||||
{
|
{
|
||||||
case IJobServer js:
|
var jobServer = await InitializeJobServerAsync(systemConnection);
|
||||||
{
|
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = detailInfo };
|
||||||
await LogWorkerProcessUnhandledException(js, message, unhandledExceptionIssue);
|
unhandledExceptionIssue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.WorkerCrash;
|
||||||
// Go ahead to finish the job with result 'Failed' if the STDERR from worker is System.IO.IOException, since it typically means we are running out of disk space.
|
switch (jobServer)
|
||||||
if (detailInfo.Contains(typeof(System.IO.IOException).ToString(), StringComparison.OrdinalIgnoreCase))
|
{
|
||||||
|
case IJobServer js:
|
||||||
{
|
{
|
||||||
Trace.Info($"Finish job with result 'Failed' due to IOException.");
|
await LogWorkerProcessUnhandledException(js, message, unhandledExceptionIssue);
|
||||||
await ForceFailJob(js, message);
|
// Go ahead to finish the job with result 'Failed' if the STDERR from worker is System.IO.IOException, since it typically means we are running out of disk space.
|
||||||
}
|
if (detailInfo.Contains(typeof(System.IO.IOException).ToString(), StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
Trace.Info($"Finish job with result 'Failed' due to IOException.");
|
||||||
|
await ForceFailJob(js, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IRunServer rs:
|
||||||
|
await ForceFailJob(rs, message, unhandledExceptionIssue);
|
||||||
break;
|
break;
|
||||||
}
|
default:
|
||||||
case IRunServer rs:
|
throw new NotSupportedException($"JobServer type '{jobServer.GetType().Name}' is not supported.");
|
||||||
await ForceFailJob(rs, message, unhandledExceptionIssue);
|
}
|
||||||
break;
|
}
|
||||||
default:
|
catch (Exception ex)
|
||||||
throw new NotSupportedException($"JobServer type '{jobServer.GetType().Name}' is not supported.");
|
{
|
||||||
|
Trace.Error($"Catch exception during log worker process unhandled exception.");
|
||||||
|
Trace.Error(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1198,7 +1206,7 @@ namespace GitHub.Runner.Listener
|
|||||||
jobAnnotations.Add(annotation.Value);
|
jobAnnotations.Add(annotation.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, TaskResult.Failed, outputs: null, stepResults: null, jobAnnotations: jobAnnotations, environmentUrl: null, telemetry: 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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ namespace GitHub.Runner.Listener
|
|||||||
Task<TaskAgentMessage> GetNextMessageAsync(CancellationToken token);
|
Task<TaskAgentMessage> GetNextMessageAsync(CancellationToken token);
|
||||||
Task DeleteMessageAsync(TaskAgentMessage message);
|
Task DeleteMessageAsync(TaskAgentMessage message);
|
||||||
|
|
||||||
Task RefreshListenerTokenAsync(CancellationToken token);
|
Task RefreshListenerTokenAsync();
|
||||||
void OnJobStatus(object sender, JobStatusEventArgs e);
|
void OnJobStatus(object sender, JobStatusEventArgs e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,6 +44,7 @@ namespace GitHub.Runner.Listener
|
|||||||
private ITerminal _term;
|
private ITerminal _term;
|
||||||
private IRunnerServer _runnerServer;
|
private IRunnerServer _runnerServer;
|
||||||
private IBrokerServer _brokerServer;
|
private IBrokerServer _brokerServer;
|
||||||
|
private ICredentialManager _credMgr;
|
||||||
private TaskAgentSession _session;
|
private TaskAgentSession _session;
|
||||||
private TimeSpan _getNextMessageRetryInterval;
|
private TimeSpan _getNextMessageRetryInterval;
|
||||||
private bool _accessTokenRevoked = false;
|
private bool _accessTokenRevoked = false;
|
||||||
@@ -54,8 +55,9 @@ namespace GitHub.Runner.Listener
|
|||||||
private TaskAgentStatus runnerStatus = TaskAgentStatus.Online;
|
private TaskAgentStatus runnerStatus = TaskAgentStatus.Online;
|
||||||
private CancellationTokenSource _getMessagesTokenSource;
|
private CancellationTokenSource _getMessagesTokenSource;
|
||||||
private VssCredentials _creds;
|
private VssCredentials _creds;
|
||||||
|
private VssCredentials _credsV2;
|
||||||
private bool _isBrokerSession = false;
|
private bool _needRefreshCredsV2 = false;
|
||||||
|
private bool _handlerInitialized = false;
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
public override void Initialize(IHostContext hostContext)
|
||||||
{
|
{
|
||||||
@@ -64,6 +66,7 @@ namespace GitHub.Runner.Listener
|
|||||||
_term = HostContext.GetService<ITerminal>();
|
_term = HostContext.GetService<ITerminal>();
|
||||||
_runnerServer = HostContext.GetService<IRunnerServer>();
|
_runnerServer = HostContext.GetService<IRunnerServer>();
|
||||||
_brokerServer = hostContext.GetService<IBrokerServer>();
|
_brokerServer = hostContext.GetService<IBrokerServer>();
|
||||||
|
_credMgr = hostContext.GetService<ICredentialManager>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CreateSessionResult> CreateSessionAsync(CancellationToken token)
|
public async Task<CreateSessionResult> CreateSessionAsync(CancellationToken token)
|
||||||
@@ -78,8 +81,7 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
// Create connection.
|
// Create connection.
|
||||||
Trace.Info("Loading Credentials");
|
Trace.Info("Loading Credentials");
|
||||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
_creds = _credMgr.LoadCredentials(allowAuthUrlV2: false);
|
||||||
_creds = credMgr.LoadCredentials();
|
|
||||||
|
|
||||||
var agent = new TaskAgentReference
|
var agent = new TaskAgentReference
|
||||||
{
|
{
|
||||||
@@ -113,16 +115,6 @@ namespace GitHub.Runner.Listener
|
|||||||
_settings.PoolId,
|
_settings.PoolId,
|
||||||
taskAgentSession,
|
taskAgentSession,
|
||||||
token);
|
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.");
|
Trace.Info($"Session created.");
|
||||||
if (encounteringError)
|
if (encounteringError)
|
||||||
{
|
{
|
||||||
@@ -131,6 +123,13 @@ namespace GitHub.Runner.Listener
|
|||||||
encounteringError = false;
|
encounteringError = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_handlerInitialized)
|
||||||
|
{
|
||||||
|
Trace.Info("Registering AuthMigrationChanged event handler.");
|
||||||
|
HostContext.AuthMigrationChanged += HandleAuthMigrationChanged;
|
||||||
|
_handlerInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
return CreateSessionResult.Success;
|
return CreateSessionResult.Success;
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||||
@@ -196,16 +195,16 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
if (_session != null && _session.SessionId != Guid.Empty)
|
if (_session != null && _session.SessionId != Guid.Empty)
|
||||||
{
|
{
|
||||||
|
if (_handlerInitialized)
|
||||||
|
{
|
||||||
|
HostContext.AuthMigrationChanged -= HandleAuthMigrationChanged;
|
||||||
|
}
|
||||||
|
|
||||||
if (!_accessTokenRevoked)
|
if (!_accessTokenRevoked)
|
||||||
{
|
{
|
||||||
using (var ts = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
|
using (var ts = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
|
||||||
{
|
{
|
||||||
await _runnerServer.DeleteAgentSessionAsync(_settings.PoolId, _session.SessionId, ts.Token);
|
await _runnerServer.DeleteAgentSessionAsync(_settings.PoolId, _session.SessionId, ts.Token);
|
||||||
|
|
||||||
if (_isBrokerSession)
|
|
||||||
{
|
|
||||||
await _brokerServer.DeleteSessionAsync(ts.Token);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -261,12 +260,19 @@ namespace GitHub.Runner.Listener
|
|||||||
// Decrypt the message body if the session is using encryption
|
// Decrypt the message body if the session is using encryption
|
||||||
message = DecryptMessage(message);
|
message = DecryptMessage(message);
|
||||||
|
|
||||||
|
|
||||||
if (message != null && message.MessageType == BrokerMigrationMessage.MessageType)
|
if (message != null && message.MessageType == BrokerMigrationMessage.MessageType)
|
||||||
{
|
{
|
||||||
var migrationMessage = JsonUtility.FromString<BrokerMigrationMessage>(message.Body);
|
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,
|
message = await _brokerServer.GetRunnerMessageAsync(_session.SessionId,
|
||||||
runnerStatus,
|
runnerStatus,
|
||||||
BuildConstants.RunnerPackage.Version,
|
BuildConstants.RunnerPackage.Version,
|
||||||
@@ -304,6 +310,11 @@ namespace GitHub.Runner.Listener
|
|||||||
_accessTokenRevoked = true;
|
_accessTokenRevoked = true;
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
catch (HostedRunnerDeprovisionedException)
|
||||||
|
{
|
||||||
|
Trace.Info("Hosted runner has been deprovisioned.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
catch (AccessDeniedException e) when (e.ErrorCode == 1)
|
catch (AccessDeniedException e) when (e.ErrorCode == 1)
|
||||||
{
|
{
|
||||||
throw;
|
throw;
|
||||||
@@ -317,6 +328,10 @@ namespace GitHub.Runner.Listener
|
|||||||
Trace.Error("Catch exception during get next message.");
|
Trace.Error("Catch exception during get next message.");
|
||||||
Trace.Error(ex);
|
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.
|
// 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 (ex is TaskAgentSessionExpiredException && !_settings.SkipSessionRecover && (await CreateSessionAsync(token) == CreateSessionResult.Success))
|
||||||
{
|
{
|
||||||
@@ -349,6 +364,12 @@ namespace GitHub.Runner.Listener
|
|||||||
encounteringError = true;
|
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
|
// re-create VssConnection before next retry
|
||||||
await _runnerServer.RefreshConnectionAsync(RunnerConnectionType.MessageQueue, TimeSpan.FromSeconds(60));
|
await _runnerServer.RefreshConnectionAsync(RunnerConnectionType.MessageQueue, TimeSpan.FromSeconds(60));
|
||||||
|
|
||||||
@@ -406,10 +427,11 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RefreshListenerTokenAsync(CancellationToken cancellationToken)
|
public async Task RefreshListenerTokenAsync()
|
||||||
{
|
{
|
||||||
await _runnerServer.RefreshConnectionAsync(RunnerConnectionType.MessageQueue, TimeSpan.FromSeconds(60));
|
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)
|
private TaskAgentMessage DecryptMessage(TaskAgentMessage message)
|
||||||
@@ -528,7 +550,8 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
else if (ex is TaskAgentPoolNotFoundException ||
|
else if (ex is TaskAgentPoolNotFoundException ||
|
||||||
ex is AccessDeniedException ||
|
ex is AccessDeniedException ||
|
||||||
ex is VssUnauthorizedException)
|
ex is VssUnauthorizedException ||
|
||||||
|
(ex is VssOAuthTokenRequestException oauthEx && oauthEx.Error != "server_error"))
|
||||||
{
|
{
|
||||||
Trace.Info($"Non-retriable exception: {ex.Message}");
|
Trace.Info($"Non-retriable exception: {ex.Message}");
|
||||||
return false;
|
return false;
|
||||||
@@ -539,5 +562,11 @@ namespace GitHub.Runner.Listener
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleAuthMigrationChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
Trace.Info($"Auth migration changed. Current allow auth migration state: {HostContext.AllowAuthMigration}");
|
||||||
|
_needRefreshCredsV2 = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -31,6 +32,13 @@ namespace GitHub.Runner.Listener
|
|||||||
private ITerminal _term;
|
private ITerminal _term;
|
||||||
private bool _inConfigStage;
|
private bool _inConfigStage;
|
||||||
private ManualResetEvent _completedCommand = new(false);
|
private ManualResetEvent _completedCommand = new(false);
|
||||||
|
private readonly ConcurrentQueue<string> _authMigrationTelemetries = new();
|
||||||
|
private Task _authMigrationTelemetryTask;
|
||||||
|
private readonly object _authMigrationTelemetryLock = new();
|
||||||
|
private IRunnerServer _runnerServer;
|
||||||
|
private CancellationTokenSource _authMigrationTelemetryTokenSource = new();
|
||||||
|
private bool _runnerExiting = false;
|
||||||
|
private bool _hasTerminationGracePeriod = false;
|
||||||
|
|
||||||
// <summary>
|
// <summary>
|
||||||
// Helps avoid excessive calls to Run Service when encountering non-retriable errors from /acquirejob.
|
// Helps avoid excessive calls to Run Service when encountering non-retriable errors from /acquirejob.
|
||||||
@@ -51,6 +59,7 @@ namespace GitHub.Runner.Listener
|
|||||||
base.Initialize(hostContext);
|
base.Initialize(hostContext);
|
||||||
_term = HostContext.GetService<ITerminal>();
|
_term = HostContext.GetService<ITerminal>();
|
||||||
_acquireJobThrottler = HostContext.CreateService<IErrorThrottler>();
|
_acquireJobThrottler = HostContext.CreateService<IErrorThrottler>();
|
||||||
|
_runnerServer = HostContext.GetService<IRunnerServer>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int> ExecuteCommand(CommandSettings command)
|
public async Task<int> ExecuteCommand(CommandSettings command)
|
||||||
@@ -66,6 +75,8 @@ namespace GitHub.Runner.Listener
|
|||||||
//register a SIGTERM handler
|
//register a SIGTERM handler
|
||||||
HostContext.Unloading += Runner_Unloading;
|
HostContext.Unloading += Runner_Unloading;
|
||||||
|
|
||||||
|
HostContext.AuthMigrationChanged += HandleAuthMigrationChanged;
|
||||||
|
|
||||||
// TODO Unit test to cover this logic
|
// TODO Unit test to cover this logic
|
||||||
Trace.Info(nameof(ExecuteCommand));
|
Trace.Info(nameof(ExecuteCommand));
|
||||||
var configManager = HostContext.GetService<IConfigurationManager>();
|
var configManager = HostContext.GetService<IConfigurationManager>();
|
||||||
@@ -300,6 +311,21 @@ 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);
|
_term.WriteLine("https://docs.github.com/en/actions/hosting-your-own-runners/autoscaling-with-self-hosted-runners#using-ephemeral-runners-for-autoscaling", ConsoleColor.Yellow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable(Constants.Variables.Agent.ActionsTerminationGracePeriodSeconds)))
|
||||||
|
{
|
||||||
|
_hasTerminationGracePeriod = true;
|
||||||
|
Trace.Verbose($"Runner has termination grace period set");
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
// Run the runner interactively or as service
|
||||||
return await RunAsync(settings, command.RunOnce || settings.Ephemeral);
|
return await RunAsync(settings, command.RunOnce || settings.Ephemeral);
|
||||||
}
|
}
|
||||||
@@ -311,6 +337,8 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
_authMigrationTelemetryTokenSource?.Cancel();
|
||||||
|
HostContext.AuthMigrationChanged -= HandleAuthMigrationChanged;
|
||||||
_term.CancelKeyPress -= CtrlCHandler;
|
_term.CancelKeyPress -= CtrlCHandler;
|
||||||
HostContext.Unloading -= Runner_Unloading;
|
HostContext.Unloading -= Runner_Unloading;
|
||||||
_completedCommand.Set();
|
_completedCommand.Set();
|
||||||
@@ -319,9 +347,10 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
private void Runner_Unloading(object sender, EventArgs e)
|
private void Runner_Unloading(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
_runnerExiting = true;
|
||||||
if ((!_inConfigStage) && (!HostContext.RunnerShutdownToken.IsCancellationRequested))
|
if ((!_inConfigStage) && (!HostContext.RunnerShutdownToken.IsCancellationRequested))
|
||||||
{
|
{
|
||||||
HostContext.ShutdownRunner(ShutdownReason.UserCancelled);
|
HostContext.ShutdownRunner(ShutdownReason.UserCancelled, GetShutdownDelay());
|
||||||
_completedCommand.WaitOne(Constants.Runner.ExitOnUnloadTimeout);
|
_completedCommand.WaitOne(Constants.Runner.ExitOnUnloadTimeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -329,6 +358,7 @@ namespace GitHub.Runner.Listener
|
|||||||
private void CtrlCHandler(object sender, EventArgs e)
|
private void CtrlCHandler(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
_term.WriteLine("Exiting...");
|
_term.WriteLine("Exiting...");
|
||||||
|
_runnerExiting = true;
|
||||||
if (_inConfigStage)
|
if (_inConfigStage)
|
||||||
{
|
{
|
||||||
HostContext.Dispose();
|
HostContext.Dispose();
|
||||||
@@ -351,16 +381,28 @@ namespace GitHub.Runner.Listener
|
|||||||
reason = ShutdownReason.UserCancelled;
|
reason = ShutdownReason.UserCancelled;
|
||||||
}
|
}
|
||||||
|
|
||||||
HostContext.ShutdownRunner(reason);
|
HostContext.ShutdownRunner(reason, GetShutdownDelay());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HostContext.ShutdownRunner(ShutdownReason.UserCancelled);
|
HostContext.ShutdownRunner(ShutdownReason.UserCancelled, GetShutdownDelay());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IMessageListener GetMesageListener(RunnerSettings settings)
|
private void HandleJobStatusEvent(object sender, JobStatusEventArgs e)
|
||||||
|
{
|
||||||
|
if (_hasTerminationGracePeriod &&
|
||||||
|
e != null &&
|
||||||
|
e.Status != TaskAgentStatus.Busy &&
|
||||||
|
_runnerExiting)
|
||||||
|
{
|
||||||
|
Trace.Info("Runner is no longer busy, shutting down.");
|
||||||
|
HostContext.ShutdownRunner(ShutdownReason.UserCancelled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IMessageListener GetMessageListener(RunnerSettings settings)
|
||||||
{
|
{
|
||||||
if (settings.UseV2Flow)
|
if (settings.UseV2Flow)
|
||||||
{
|
{
|
||||||
@@ -379,7 +421,7 @@ namespace GitHub.Runner.Listener
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Trace.Info(nameof(RunAsync));
|
Trace.Info(nameof(RunAsync));
|
||||||
_listener = GetMesageListener(settings);
|
_listener = GetMessageListener(settings);
|
||||||
CreateSessionResult createSessionResult = await _listener.CreateSessionAsync(HostContext.RunnerShutdownToken);
|
CreateSessionResult createSessionResult = await _listener.CreateSessionAsync(HostContext.RunnerShutdownToken);
|
||||||
if (createSessionResult == CreateSessionResult.SessionConflict)
|
if (createSessionResult == CreateSessionResult.SessionConflict)
|
||||||
{
|
{
|
||||||
@@ -410,9 +452,13 @@ namespace GitHub.Runner.Listener
|
|||||||
bool autoUpdateInProgress = false;
|
bool autoUpdateInProgress = false;
|
||||||
Task<bool> selfUpdateTask = null;
|
Task<bool> selfUpdateTask = null;
|
||||||
bool runOnceJobReceived = false;
|
bool runOnceJobReceived = false;
|
||||||
jobDispatcher = HostContext.CreateService<IJobDispatcher>();
|
jobDispatcher = HostContext.GetService<IJobDispatcher>();
|
||||||
|
|
||||||
jobDispatcher.JobStatus += _listener.OnJobStatus;
|
jobDispatcher.JobStatus += _listener.OnJobStatus;
|
||||||
|
if (_hasTerminationGracePeriod)
|
||||||
|
{
|
||||||
|
jobDispatcher.JobStatus += HandleJobStatusEvent;
|
||||||
|
}
|
||||||
|
|
||||||
while (!HostContext.RunnerShutdownToken.IsCancellationRequested)
|
while (!HostContext.RunnerShutdownToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@@ -570,21 +616,21 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
// Create connection
|
// Create connection
|
||||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
var credMgr = HostContext.GetService<ICredentialManager>();
|
||||||
var creds = credMgr.LoadCredentials();
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(messageRef.RunServiceUrl))
|
if (string.IsNullOrEmpty(messageRef.RunServiceUrl))
|
||||||
{
|
{
|
||||||
|
var creds = credMgr.LoadCredentials(allowAuthUrlV2: false);
|
||||||
var actionsRunServer = HostContext.CreateService<IActionsRunServer>();
|
var actionsRunServer = HostContext.CreateService<IActionsRunServer>();
|
||||||
await actionsRunServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
await actionsRunServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
||||||
jobRequestMessage = await actionsRunServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
jobRequestMessage = await actionsRunServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
var credsV2 = credMgr.LoadCredentials(allowAuthUrlV2: true);
|
||||||
var runServer = HostContext.CreateService<IRunServer>();
|
var runServer = HostContext.CreateService<IRunServer>();
|
||||||
await runServer.ConnectAsync(new Uri(messageRef.RunServiceUrl), creds);
|
await runServer.ConnectAsync(new Uri(messageRef.RunServiceUrl), credsV2);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
jobRequestMessage = await runServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
jobRequestMessage = await runServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageRef.BillingOwnerId, messageQueueLoopTokenSource.Token);
|
||||||
_acquireJobThrottler.Reset();
|
_acquireJobThrottler.Reset();
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (
|
catch (Exception ex) when (
|
||||||
@@ -599,6 +645,13 @@ namespace GitHub.Runner.Listener
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Trace.Error($"Caught exception from acquiring job message: {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;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -633,7 +686,18 @@ namespace GitHub.Runner.Listener
|
|||||||
else if (string.Equals(message.MessageType, TaskAgentMessageTypes.ForceTokenRefresh))
|
else if (string.Equals(message.MessageType, TaskAgentMessageTypes.ForceTokenRefresh))
|
||||||
{
|
{
|
||||||
Trace.Info("Received ForceTokenRefreshMessage");
|
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);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -665,6 +729,10 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
if (jobDispatcher != null)
|
if (jobDispatcher != null)
|
||||||
{
|
{
|
||||||
|
if (_hasTerminationGracePeriod)
|
||||||
|
{
|
||||||
|
jobDispatcher.JobStatus -= HandleJobStatusEvent;
|
||||||
|
}
|
||||||
jobDispatcher.JobStatus -= _listener.OnJobStatus;
|
jobDispatcher.JobStatus -= _listener.OnJobStatus;
|
||||||
await jobDispatcher.ShutdownAsync();
|
await jobDispatcher.ShutdownAsync();
|
||||||
}
|
}
|
||||||
@@ -697,10 +765,109 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
Trace.Info("Runner OAuth token has been revoked. Shutting down.");
|
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;
|
return Constants.Runner.ReturnCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 TimeSpan GetShutdownDelay()
|
||||||
|
{
|
||||||
|
TimeSpan delay = TimeSpan.Zero;
|
||||||
|
if (_hasTerminationGracePeriod)
|
||||||
|
{
|
||||||
|
var jobDispatcher = HostContext.GetService<IJobDispatcher>();
|
||||||
|
if (jobDispatcher.Busy)
|
||||||
|
{
|
||||||
|
Trace.Info("Runner is busy, checking for grace period.");
|
||||||
|
var delayEnv = Environment.GetEnvironmentVariable(Constants.Variables.Agent.ActionsTerminationGracePeriodSeconds);
|
||||||
|
if (!string.IsNullOrEmpty(delayEnv) &&
|
||||||
|
int.TryParse(delayEnv, out int delaySeconds) &&
|
||||||
|
delaySeconds > 0 &&
|
||||||
|
delaySeconds < 60 * 60) // 1 hour
|
||||||
|
{
|
||||||
|
Trace.Info($"Waiting for {delaySeconds} seconds before shutting down.");
|
||||||
|
delay = TimeSpan.FromSeconds(delaySeconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Trace.Verbose("Runner is not busy, no grace period.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return delay;
|
||||||
|
}
|
||||||
|
|
||||||
private void PrintUsage(CommandSettings command)
|
private void PrintUsage(CommandSettings command)
|
||||||
{
|
{
|
||||||
string separator;
|
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")]
|
[DataMember(Name = "id")]
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
|
|
||||||
[DataMember(Name = "runner_request_id")]
|
[DataMember(Name = "runner_request_id")]
|
||||||
public string RunnerRequestId { get; set; }
|
public string RunnerRequestId { get; set; }
|
||||||
|
|
||||||
[DataMember(Name = "run_service_url")]
|
[DataMember(Name = "run_service_url")]
|
||||||
public string RunServiceUrl { get; set; }
|
public string RunServiceUrl { get; set; }
|
||||||
|
|
||||||
|
[DataMember(Name = "billing_owner_id")]
|
||||||
|
public string BillingOwnerId { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -775,7 +775,19 @@ namespace GitHub.Runner.Worker
|
|||||||
// make sure we get a clean folder ready to use.
|
// make sure we get a clean folder ready to use.
|
||||||
IOUtil.DeleteDirectory(destDirectory, executionContext.CancellationToken);
|
IOUtil.DeleteDirectory(destDirectory, executionContext.CancellationToken);
|
||||||
Directory.CreateDirectory(destDirectory);
|
Directory.CreateDirectory(destDirectory);
|
||||||
executionContext.Output($"Download action repository '{downloadInfo.NameWithOwner}@{downloadInfo.Ref}' (SHA:{downloadInfo.ResolvedSha})");
|
|
||||||
|
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
|
//download and extract action in a temp folder and rename it on success
|
||||||
|
|||||||
@@ -318,9 +318,23 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, result, jobContext.JobOutputs, jobContext.Global.StepsResult, jobContext.Global.JobAnnotations, environmentUrl, telemetry, 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;
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Trace.Error($"Catch exception while attempting to complete job {message.JobId}, job request {message.RequestId}.");
|
Trace.Error($"Catch exception while attempting to complete job {message.JobId}, job request {message.RequestId}.");
|
||||||
@@ -511,7 +525,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
if (result == TaskResult.Failed && warnOnFailedJob)
|
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}");
|
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)
|
else if (warnOnOldRunnerVersion)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.Tracing;
|
using System.Diagnostics.Tracing;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
@@ -335,7 +336,25 @@ namespace GitHub.Services.Common.Diagnostics
|
|||||||
if (IsEnabled())
|
if (IsEnabled())
|
||||||
{
|
{
|
||||||
SetActivityId(activity);
|
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(
|
private void HttpRequestStop(
|
||||||
VssHttpMethod method,
|
VssHttpMethod method,
|
||||||
String url,
|
String url,
|
||||||
Int32 statusCode)
|
Int32 statusCode,
|
||||||
|
String requestId)
|
||||||
{
|
{
|
||||||
if (IsEnabled())
|
if (IsEnabled())
|
||||||
{
|
{
|
||||||
WriteEvent(24, (Int32)method, url, statusCode);
|
WriteEvent(24, (Int32)method, url, statusCode, requestId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ using System.IO;
|
|||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using System.Net.Http.Formatting;
|
using System.Net.Http.Formatting;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.Services.Common;
|
using GitHub.Services.Common;
|
||||||
@@ -827,5 +827,36 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
userState: userState,
|
userState: userState,
|
||||||
cancellationToken: cancellationToken);
|
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;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public String BillingOwnerId
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the collection of variables associated with the current context.
|
/// Gets the collection of variables associated with the current context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
{
|
{
|
||||||
if (!NameValidation.IsValid(value, allowHyphens: true) && value.Length < PipelineConstants.MaxNodeNameLength)
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
else if (!m_distinctNames.Add(value))
|
else if (!m_distinctNames.Add(value))
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
[DataMember(EmitDefaultValue = false)]
|
[DataMember(EmitDefaultValue = false)]
|
||||||
public ActionDownloadAuthentication Authentication { get; set; }
|
public ActionDownloadAuthentication Authentication { get; set; }
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public ActionDownloadPackageDetails PackageDetails { get; set; }
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
[DataMember(EmitDefaultValue = false)]
|
||||||
public string NameWithOwner { get; set; }
|
public string NameWithOwner { get; set; }
|
||||||
|
|
||||||
@@ -37,4 +40,14 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
[DataMember(EmitDefaultValue = false)]
|
[DataMember(EmitDefaultValue = false)]
|
||||||
public string Token { get; set; }
|
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>
|
/// <summary>
|
||||||
/// The url to connect to to poll for messages
|
/// The url to connect to poll for messages
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("server_url")]
|
[JsonProperty("server_url")]
|
||||||
public string ServerUrl
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,5 +10,8 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
|
|
||||||
[DataMember(Name = "runnerOS", EmitDefaultValue = false)]
|
[DataMember(Name = "runnerOS", EmitDefaultValue = false)]
|
||||||
public string RunnerOS { get; set; }
|
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)]
|
[DataMember(Name = "message", EmitDefaultValue = false)]
|
||||||
public string Message;
|
public string Message;
|
||||||
|
|
||||||
|
[DataMember(Name = "title", EmitDefaultValue = false)]
|
||||||
|
public string Title;
|
||||||
|
|
||||||
[DataMember(Name = "rawDetails", EmitDefaultValue = false)]
|
[DataMember(Name = "rawDetails", EmitDefaultValue = false)]
|
||||||
public string RawDetails;
|
public string RawDetails;
|
||||||
|
|
||||||
@@ -31,5 +34,8 @@ namespace Sdk.RSWebApi.Contracts
|
|||||||
|
|
||||||
[DataMember(Name = "endColumn", EmitDefaultValue = false)]
|
[DataMember(Name = "endColumn", EmitDefaultValue = false)]
|
||||||
public long EndColumn;
|
public long EndColumn;
|
||||||
|
|
||||||
|
[DataMember(Name = "stepNumber", EmitDefaultValue = false)]
|
||||||
|
public long StepNumber;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
{
|
{
|
||||||
public const string RunnerNotFound = "RunnerNotFound";
|
public const string RunnerNotFound = "RunnerNotFound";
|
||||||
public const string RunnerVersionTooOld = "RunnerVersionTooOld";
|
public const string RunnerVersionTooOld = "RunnerVersionTooOld";
|
||||||
|
public const string HostedRunnerDeprovisioned = "HostedRunnerDeprovisioned";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,5 +32,8 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
|
|
||||||
[DataMember(Name = "environmentUrl", EmitDefaultValue = false)]
|
[DataMember(Name = "environmentUrl", EmitDefaultValue = false)]
|
||||||
public string EnvironmentUrl { get; set; }
|
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 columnNumber = GetAnnotationNumber(issue, RunIssueKeys.Col) ?? 0;
|
||||||
var endColumnNumber = GetAnnotationNumber(issue, RunIssueKeys.EndColumn) ?? columnNumber;
|
var endColumnNumber = GetAnnotationNumber(issue, RunIssueKeys.EndColumn) ?? columnNumber;
|
||||||
var logLineNumber = GetAnnotationNumber(issue, RunIssueKeys.LogLineNumber) ?? 0;
|
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)
|
if (path == null && lineNumber == 0 && logLineNumber != 0)
|
||||||
{
|
{
|
||||||
@@ -33,11 +35,13 @@ namespace Sdk.RSWebApi.Contracts
|
|||||||
{
|
{
|
||||||
Level = annotationLevel,
|
Level = annotationLevel,
|
||||||
Message = issueMessage,
|
Message = issueMessage,
|
||||||
|
Title = title,
|
||||||
Path = path,
|
Path = path,
|
||||||
StartLine = lineNumber,
|
StartLine = lineNumber,
|
||||||
EndLine = endLineNumber,
|
EndLine = endLineNumber,
|
||||||
StartColumn = columnNumber,
|
StartColumn = columnNumber,
|
||||||
EndColumn = endColumnNumber,
|
EndColumn = endColumnNumber,
|
||||||
|
StepNumber = stepNumber,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,5 +9,7 @@
|
|||||||
public const string EndLine = "endLine";
|
public const string EndLine = "endLine";
|
||||||
public const string EndColumn = "endColumn";
|
public const string EndColumn = "endColumn";
|
||||||
public const string LogLineNumber = "logFileLineNumber";
|
public const string LogLineNumber = "logFileLineNumber";
|
||||||
|
public const string StepNumber = "stepNumber";
|
||||||
|
public const string Title = "title";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,13 +71,15 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
Uri requestUri,
|
Uri requestUri,
|
||||||
string messageId,
|
string messageId,
|
||||||
string runnerOS,
|
string runnerOS,
|
||||||
|
string billingOwnerId,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
HttpMethod httpMethod = new HttpMethod("POST");
|
HttpMethod httpMethod = new HttpMethod("POST");
|
||||||
var payload = new AcquireJobRequest
|
var payload = new AcquireJobRequest
|
||||||
{
|
{
|
||||||
JobMessageId = messageId,
|
JobMessageId = messageId,
|
||||||
RunnerOS = runnerOS
|
RunnerOS = runnerOS,
|
||||||
|
BillingOwnerId = billingOwnerId,
|
||||||
};
|
};
|
||||||
|
|
||||||
requestUri = new Uri(requestUri, "acquirejob");
|
requestUri = new Uri(requestUri, "acquirejob");
|
||||||
@@ -128,6 +130,7 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
IList<Annotation> jobAnnotations,
|
IList<Annotation> jobAnnotations,
|
||||||
string environmentUrl,
|
string environmentUrl,
|
||||||
IList<Telemetry> telemetry,
|
IList<Telemetry> telemetry,
|
||||||
|
string billingOwnerId,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
HttpMethod httpMethod = new HttpMethod("POST");
|
HttpMethod httpMethod = new HttpMethod("POST");
|
||||||
@@ -141,6 +144,7 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
Annotations = jobAnnotations,
|
Annotations = jobAnnotations,
|
||||||
EnvironmentUrl = environmentUrl,
|
EnvironmentUrl = environmentUrl,
|
||||||
Telemetry = telemetry,
|
Telemetry = telemetry,
|
||||||
|
BillingOwnerId = billingOwnerId,
|
||||||
};
|
};
|
||||||
|
|
||||||
requestUri = new Uri(requestUri, "completejob");
|
requestUri = new Uri(requestUri, "completejob");
|
||||||
@@ -249,11 +253,12 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
return false;
|
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;
|
return errorBody;
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
|
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.5.1" />
|
|
||||||
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
|
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
|
||||||
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="8.0.0" />
|
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="8.0.0" />
|
||||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
||||||
@@ -26,6 +25,8 @@
|
|||||||
<PackageReference Include="YamlDotNet.Signed" Version="5.3.0" />
|
<PackageReference Include="YamlDotNet.Signed" Version="5.3.0" />
|
||||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
<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>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -122,6 +122,8 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
{
|
{
|
||||||
ErrorCode = 1
|
ErrorCode = 1
|
||||||
};
|
};
|
||||||
|
case BrokerErrorKind.HostedRunnerDeprovisioned:
|
||||||
|
throw new HostedRunnerDeprovisionedException(brokerError.Message);
|
||||||
default:
|
default:
|
||||||
break;
|
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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,10 @@ namespace GitHub.Services.WebApi.Jwt
|
|||||||
HS256,
|
HS256,
|
||||||
|
|
||||||
[EnumMember]
|
[EnumMember]
|
||||||
RS256
|
RS256,
|
||||||
|
|
||||||
|
[EnumMember]
|
||||||
|
PS256,
|
||||||
}
|
}
|
||||||
|
|
||||||
//JsonWebToken is marked as DataContract so
|
//JsonWebToken is marked as DataContract so
|
||||||
@@ -286,6 +289,7 @@ namespace GitHub.Services.WebApi.Jwt
|
|||||||
{
|
{
|
||||||
case JWTAlgorithm.HS256:
|
case JWTAlgorithm.HS256:
|
||||||
case JWTAlgorithm.RS256:
|
case JWTAlgorithm.RS256:
|
||||||
|
case JWTAlgorithm.PS256:
|
||||||
return signingCredentials.SignData(bytes);
|
return signingCredentials.SignData(bytes);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ namespace GitHub.Services.Launch.Contracts
|
|||||||
{
|
{
|
||||||
[DataMember(EmitDefaultValue = false, Name = "authentication")]
|
[DataMember(EmitDefaultValue = false, Name = "authentication")]
|
||||||
public ActionDownloadAuthenticationResponse Authentication { get; set; }
|
public ActionDownloadAuthenticationResponse Authentication { get; set; }
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false, Name = "package_details")]
|
||||||
|
public ActionDownloadPackageDetailsResponse PackageDetails { get; set; }
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false, Name = "name")]
|
[DataMember(EmitDefaultValue = false, Name = "name")]
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
@@ -59,6 +62,17 @@ namespace GitHub.Services.Launch.Contracts
|
|||||||
public string Token { get; set; }
|
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]
|
[DataContract]
|
||||||
public class ActionDownloadInfoResponseCollection
|
public class ActionDownloadInfoResponseCollection
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ namespace GitHub.Services.Launch.Client
|
|||||||
TarballUrl = actionDownloadInfoResponse.TarUrl,
|
TarballUrl = actionDownloadInfoResponse.TarUrl,
|
||||||
Ref = actionDownloadInfoResponse.Version,
|
Ref = actionDownloadInfoResponse.Version,
|
||||||
ZipballUrl = actionDownloadInfoResponse.ZipUrl,
|
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 MediaTypeFormatter m_formatter;
|
||||||
private Uri m_launchServiceUrl;
|
private Uri m_launchServiceUrl;
|
||||||
private string m_token;
|
private string m_token;
|
||||||
|
|||||||
@@ -677,7 +677,7 @@ namespace GitHub.Services.WebApi.Location
|
|||||||
Int32 lastChangeId = m_locationDataCacheManager.GetLastChangeId();
|
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
|
// 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.
|
// 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)
|
// 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
|
// would leave the in-memory cache in an inconsistent state
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
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)
|
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()
|
var request = new StepSummaryMetadataCreate()
|
||||||
{
|
{
|
||||||
WorkflowJobRunBackendId = jobId,
|
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)
|
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()
|
var request = new StepLogsMetadataCreate()
|
||||||
{
|
{
|
||||||
WorkflowJobRunBackendId = jobId,
|
WorkflowJobRunBackendId = jobId,
|
||||||
@@ -164,7 +165,7 @@ namespace GitHub.Services.Results.Client
|
|||||||
|
|
||||||
private async Task JobLogUploadCompleteAsync(string planId, string jobId, long lineCount, CancellationToken cancellationToken)
|
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()
|
var request = new JobLogsMetadataCreate()
|
||||||
{
|
{
|
||||||
WorkflowJobRunBackendId = jobId,
|
WorkflowJobRunBackendId = jobId,
|
||||||
@@ -565,7 +566,7 @@ namespace GitHub.Services.Results.Client
|
|||||||
|
|
||||||
public async Task UpdateWorkflowStepsAsync(Guid planId, IEnumerable<TimelineRecord> records, CancellationToken cancellationToken)
|
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 stepRecords = records.Where(r => String.Equals(r.RecordType, "Task", StringComparison.Ordinal));
|
||||||
var stepUpdateRequests = stepRecords.GroupBy(r => r.ParentId).Select(sg => new StepsUpdateRequest()
|
var stepUpdateRequests = stepRecords.GroupBy(r => r.ParentId).Select(sg => new StepsUpdateRequest()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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)
|
protected override Byte[] GetSignature(Byte[] input)
|
||||||
{
|
{
|
||||||
using (var rsa = m_factory())
|
using (var rsa = m_factory())
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
using GitHub.Runner.Common.Util;
|
using System;
|
||||||
using System;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common.Tests
|
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 = "")
|
private void Setup([CallerMemberName] string testName = "")
|
||||||
{
|
{
|
||||||
_tokenSource = new CancellationTokenSource();
|
_tokenSource = new CancellationTokenSource();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -16,9 +17,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
private readonly RunnerSettings _settings;
|
private readonly RunnerSettings _settings;
|
||||||
private readonly Mock<IConfigurationManager> _config;
|
private readonly Mock<IConfigurationManager> _config;
|
||||||
private readonly Mock<IBrokerServer> _brokerServer;
|
private readonly Mock<IBrokerServer> _brokerServer;
|
||||||
|
private readonly Mock<IRunnerServer> _runnerServer;
|
||||||
private readonly Mock<ICredentialManager> _credMgr;
|
private readonly Mock<ICredentialManager> _credMgr;
|
||||||
private Mock<IConfigurationStore> _store;
|
|
||||||
|
|
||||||
|
|
||||||
public BrokerMessageListenerL0()
|
public BrokerMessageListenerL0()
|
||||||
{
|
{
|
||||||
@@ -26,14 +26,14 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_config = new Mock<IConfigurationManager>();
|
_config = new Mock<IConfigurationManager>();
|
||||||
_config.Setup(x => x.LoadSettings()).Returns(_settings);
|
_config.Setup(x => x.LoadSettings()).Returns(_settings);
|
||||||
_credMgr = new Mock<ICredentialManager>();
|
_credMgr = new Mock<ICredentialManager>();
|
||||||
_store = new Mock<IConfigurationStore>();
|
|
||||||
_brokerServer = new Mock<IBrokerServer>();
|
_brokerServer = new Mock<IBrokerServer>();
|
||||||
|
_runnerServer = new Mock<IRunnerServer>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void CreatesSession()
|
public async Task CreatesSession()
|
||||||
{
|
{
|
||||||
using (TestHostContext tc = CreateTestContext())
|
using (TestHostContext tc = CreateTestContext())
|
||||||
using (var tokenSource = new CancellationTokenSource())
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
@@ -48,9 +48,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
tokenSource.Token))
|
tokenSource.Token))
|
||||||
.Returns(Task.FromResult(expectedSession));
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
_credMgr.Setup(x => x.LoadCredentials()).Returns(new VssCredentials());
|
_credMgr.Setup(x => x.LoadCredentials(true)).Returns(new VssCredentials());
|
||||||
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
|
||||||
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
BrokerMessageListener listener = new();
|
BrokerMessageListener listener = new();
|
||||||
@@ -68,13 +66,310 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task HandleAuthMigrationChanged()
|
||||||
|
{
|
||||||
|
using (TestHostContext tc = CreateTestContext())
|
||||||
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
|
{
|
||||||
|
Tracing trace = tc.GetTrace();
|
||||||
|
|
||||||
|
// Arrange.
|
||||||
|
var expectedSession = new TaskAgentSession();
|
||||||
|
_brokerServer
|
||||||
|
.Setup(x => x.CreateSessionAsync(
|
||||||
|
It.Is<TaskAgentSession>(y => y != null),
|
||||||
|
tokenSource.Token))
|
||||||
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
|
_credMgr.Setup(x => x.LoadCredentials(true)).Returns(new VssCredentials());
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
BrokerMessageListener listener = new();
|
||||||
|
listener.Initialize(tc);
|
||||||
|
|
||||||
|
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||||
|
trace.Info("result: {0}", result);
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.Equal(CreateSessionResult.Success, result);
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.CreateSessionAsync(
|
||||||
|
It.Is<TaskAgentSession>(y => y != null),
|
||||||
|
tokenSource.Token), Times.Once());
|
||||||
|
|
||||||
|
tc.EnableAuthMigration("L0Test");
|
||||||
|
|
||||||
|
var traceFile = Path.GetTempFileName();
|
||||||
|
File.Copy(tc.TraceFileName, traceFile, true);
|
||||||
|
Assert.Contains("Auth migration changed", File.ReadAllText(traceFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task CreatesSession_DeferAuthMigration()
|
||||||
|
{
|
||||||
|
using (TestHostContext tc = CreateTestContext())
|
||||||
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
|
{
|
||||||
|
Tracing trace = tc.GetTrace();
|
||||||
|
|
||||||
|
// Arrange.
|
||||||
|
var throwException = true;
|
||||||
|
var expectedSession = new TaskAgentSession();
|
||||||
|
_brokerServer
|
||||||
|
.Setup(x => x.CreateSessionAsync(
|
||||||
|
It.Is<TaskAgentSession>(y => y != null),
|
||||||
|
tokenSource.Token))
|
||||||
|
.Returns(async (TaskAgentSession session, CancellationToken token) =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
if (throwException)
|
||||||
|
{
|
||||||
|
throwException = false;
|
||||||
|
throw new NotSupportedException("Error during create session");
|
||||||
|
}
|
||||||
|
|
||||||
|
return expectedSession;
|
||||||
|
});
|
||||||
|
|
||||||
|
_credMgr.Setup(x => x.LoadCredentials(true)).Returns(new VssCredentials());
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
BrokerMessageListener listener = new();
|
||||||
|
listener.Initialize(tc);
|
||||||
|
|
||||||
|
tc.EnableAuthMigration("L0Test");
|
||||||
|
Assert.True(tc.AllowAuthMigration);
|
||||||
|
|
||||||
|
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||||
|
trace.Info("result: {0}", result);
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.Equal(CreateSessionResult.Success, result);
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.CreateSessionAsync(
|
||||||
|
It.Is<TaskAgentSession>(y => y != null),
|
||||||
|
tokenSource.Token), Times.Exactly(2));
|
||||||
|
_credMgr.Verify(x => x.LoadCredentials(true), Times.Exactly(2));
|
||||||
|
|
||||||
|
Assert.False(tc.AllowAuthMigration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task GetNextMessage()
|
||||||
|
{
|
||||||
|
using (TestHostContext tc = CreateTestContext())
|
||||||
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
|
{
|
||||||
|
Tracing trace = tc.GetTrace();
|
||||||
|
|
||||||
|
// Arrange.
|
||||||
|
_credMgr.Setup(x => x.LoadCredentials(true)).Returns(new VssCredentials());
|
||||||
|
|
||||||
|
var expectedSession = new TaskAgentSession();
|
||||||
|
_brokerServer
|
||||||
|
.Setup(x => x.CreateSessionAsync(
|
||||||
|
It.Is<TaskAgentSession>(y => y != null),
|
||||||
|
tokenSource.Token))
|
||||||
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
|
var expectedMessage = new TaskAgentMessage();
|
||||||
|
_brokerServer
|
||||||
|
.Setup(x => x.GetRunnerMessageAsync(
|
||||||
|
It.IsAny<Guid?>(),
|
||||||
|
It.IsAny<TaskAgentStatus>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<bool>(),
|
||||||
|
It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(expectedMessage));
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
BrokerMessageListener listener = new();
|
||||||
|
listener.Initialize(tc);
|
||||||
|
|
||||||
|
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||||
|
trace.Info("result: {0}", result);
|
||||||
|
Assert.Equal(CreateSessionResult.Success, result);
|
||||||
|
|
||||||
|
TaskAgentMessage message = await listener.GetNextMessageAsync(tokenSource.Token);
|
||||||
|
trace.Info("message: {0}", message);
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.Equal(expectedMessage, message);
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.GetRunnerMessageAsync(
|
||||||
|
It.IsAny<Guid?>(),
|
||||||
|
It.IsAny<TaskAgentStatus>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<bool>(),
|
||||||
|
It.IsAny<CancellationToken>()), Times.Once());
|
||||||
|
|
||||||
|
_brokerServer.Verify(x => x.ConnectAsync(It.IsAny<Uri>(), It.IsAny<VssCredentials>()), Times.Once());
|
||||||
|
|
||||||
|
_credMgr.Verify(x => x.LoadCredentials(true), Times.Once());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task GetNextMessage_EnableAuthMigration()
|
||||||
|
{
|
||||||
|
using (TestHostContext tc = CreateTestContext())
|
||||||
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
|
{
|
||||||
|
Tracing trace = tc.GetTrace();
|
||||||
|
|
||||||
|
// Arrange.
|
||||||
|
_credMgr.Setup(x => x.LoadCredentials(true)).Returns(new VssCredentials());
|
||||||
|
|
||||||
|
var expectedSession = new TaskAgentSession();
|
||||||
|
_brokerServer
|
||||||
|
.Setup(x => x.CreateSessionAsync(
|
||||||
|
It.Is<TaskAgentSession>(y => y != null),
|
||||||
|
tokenSource.Token))
|
||||||
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
|
var expectedMessage = new TaskAgentMessage();
|
||||||
|
_brokerServer
|
||||||
|
.Setup(x => x.GetRunnerMessageAsync(
|
||||||
|
It.IsAny<Guid?>(),
|
||||||
|
It.IsAny<TaskAgentStatus>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<bool>(),
|
||||||
|
It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(expectedMessage));
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
BrokerMessageListener listener = new();
|
||||||
|
listener.Initialize(tc);
|
||||||
|
|
||||||
|
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||||
|
trace.Info("result: {0}", result);
|
||||||
|
Assert.Equal(CreateSessionResult.Success, result);
|
||||||
|
|
||||||
|
tc.EnableAuthMigration("L0Test");
|
||||||
|
|
||||||
|
TaskAgentMessage message = await listener.GetNextMessageAsync(tokenSource.Token);
|
||||||
|
trace.Info("message: {0}", message);
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.Equal(expectedMessage, message);
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.GetRunnerMessageAsync(
|
||||||
|
It.IsAny<Guid?>(),
|
||||||
|
It.IsAny<TaskAgentStatus>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<bool>(),
|
||||||
|
It.IsAny<CancellationToken>()), Times.Once());
|
||||||
|
|
||||||
|
_brokerServer.Verify(x => x.ConnectAsync(It.IsAny<Uri>(), It.IsAny<VssCredentials>()), Times.Exactly(2));
|
||||||
|
|
||||||
|
_credMgr.Verify(x => x.LoadCredentials(true), Times.Exactly(2));
|
||||||
|
|
||||||
|
Assert.True(tc.AllowAuthMigration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task GetNextMessage_AuthMigrationFallback()
|
||||||
|
{
|
||||||
|
using (TestHostContext tc = CreateTestContext())
|
||||||
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
|
{
|
||||||
|
Tracing trace = tc.GetTrace();
|
||||||
|
|
||||||
|
tc.EnableAuthMigration("L0Test");
|
||||||
|
|
||||||
|
// Arrange.
|
||||||
|
_credMgr.Setup(x => x.LoadCredentials(true)).Returns(new VssCredentials());
|
||||||
|
|
||||||
|
var expectedSession = new TaskAgentSession();
|
||||||
|
_brokerServer
|
||||||
|
.Setup(x => x.CreateSessionAsync(
|
||||||
|
It.Is<TaskAgentSession>(y => y != null),
|
||||||
|
tokenSource.Token))
|
||||||
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
|
var expectedMessage = new TaskAgentMessage();
|
||||||
|
_brokerServer
|
||||||
|
.Setup(x => x.GetRunnerMessageAsync(
|
||||||
|
It.IsAny<Guid?>(),
|
||||||
|
It.IsAny<TaskAgentStatus>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<bool>(),
|
||||||
|
It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(async (Guid? sessionId, TaskAgentStatus status, string version, string os, string architecture, bool disableUpdate, CancellationToken token) =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
if (tc.AllowAuthMigration)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Error during get message");
|
||||||
|
}
|
||||||
|
|
||||||
|
return expectedMessage;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
BrokerMessageListener listener = new();
|
||||||
|
listener.Initialize(tc);
|
||||||
|
|
||||||
|
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||||
|
trace.Info("result: {0}", result);
|
||||||
|
Assert.Equal(CreateSessionResult.Success, result);
|
||||||
|
|
||||||
|
Assert.True(tc.AllowAuthMigration);
|
||||||
|
|
||||||
|
TaskAgentMessage message = await listener.GetNextMessageAsync(tokenSource.Token);
|
||||||
|
trace.Info("message: {0}", message);
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.Equal(expectedMessage, message);
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.GetRunnerMessageAsync(
|
||||||
|
It.IsAny<Guid?>(),
|
||||||
|
It.IsAny<TaskAgentStatus>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<bool>(),
|
||||||
|
It.IsAny<CancellationToken>()), Times.Exactly(2));
|
||||||
|
|
||||||
|
_brokerServer.Verify(x => x.ConnectAsync(It.IsAny<Uri>(), It.IsAny<VssCredentials>()), Times.Exactly(3));
|
||||||
|
|
||||||
|
_credMgr.Verify(x => x.LoadCredentials(true), Times.Exactly(3));
|
||||||
|
|
||||||
|
Assert.False(tc.AllowAuthMigration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
|
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
|
||||||
{
|
{
|
||||||
TestHostContext tc = new(this, testName);
|
TestHostContext tc = new(this, testName);
|
||||||
tc.SetSingleton<IConfigurationManager>(_config.Object);
|
tc.SetSingleton<IConfigurationManager>(_config.Object);
|
||||||
tc.SetSingleton<ICredentialManager>(_credMgr.Object);
|
tc.SetSingleton<ICredentialManager>(_credMgr.Object);
|
||||||
tc.SetSingleton<IConfigurationStore>(_store.Object);
|
|
||||||
tc.SetSingleton<IBrokerServer>(_brokerServer.Object);
|
tc.SetSingleton<IBrokerServer>(_brokerServer.Object);
|
||||||
|
tc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
return tc;
|
return tc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
using GitHub.Runner.Listener;
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using GitHub.Runner.Listener;
|
||||||
using GitHub.Runner.Listener.Configuration;
|
using GitHub.Runner.Listener.Configuration;
|
||||||
using GitHub.Services.Common;
|
using GitHub.Services.Common;
|
||||||
using GitHub.Services.OAuth;
|
using GitHub.Services.OAuth;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
||||||
{
|
{
|
||||||
public class TestRunnerCredential : CredentialProvider
|
public class TestRunnerCredential : CredentialProvider
|
||||||
{
|
{
|
||||||
public TestRunnerCredential() : base("TEST") { }
|
public TestRunnerCredential() : base("TEST") { }
|
||||||
public override VssCredentials GetVssCredentials(IHostContext context)
|
public override VssCredentials GetVssCredentials(IHostContext context, bool allowAuthUrlV2)
|
||||||
{
|
{
|
||||||
Tracing trace = context.GetTrace("OuthAccessToken");
|
Tracing trace = context.GetTrace("OuthAccessToken");
|
||||||
trace.Info("GetVssCredentials()");
|
trace.Info("GetVssCredentials()");
|
||||||
@@ -23,4 +27,85 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public class OAuthCredentialTestsL0
|
||||||
|
{
|
||||||
|
private Mock<IRSAKeyManager> _rsaKeyManager = new Mock<IRSAKeyManager>();
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "OAuthCredential")]
|
||||||
|
public void NotUseAuthV2Url()
|
||||||
|
{
|
||||||
|
using (TestHostContext hc = new(this))
|
||||||
|
{
|
||||||
|
// Arrange.
|
||||||
|
var oauth = new OAuthCredential();
|
||||||
|
oauth.CredentialData = new CredentialData()
|
||||||
|
{
|
||||||
|
Scheme = Constants.Configuration.OAuth
|
||||||
|
};
|
||||||
|
oauth.CredentialData.Data.Add("clientId", "someClientId");
|
||||||
|
oauth.CredentialData.Data.Add("authorizationUrl", "http://myserver/");
|
||||||
|
oauth.CredentialData.Data.Add("authorizationUrlV2", "http://myserverv2/");
|
||||||
|
|
||||||
|
_rsaKeyManager.Setup(x => x.GetKey()).Returns(RSA.Create(2048));
|
||||||
|
hc.SetSingleton<IRSAKeyManager>(_rsaKeyManager.Object);
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
var cred = oauth.GetVssCredentials(hc, false); // not allow auth v2
|
||||||
|
|
||||||
|
var cred2 = oauth.GetVssCredentials(hc, true); // use auth v2 but hostcontext doesn't
|
||||||
|
|
||||||
|
hc.EnableAuthMigration("L0Test");
|
||||||
|
var cred3 = oauth.GetVssCredentials(hc, false); // not use auth v2 but hostcontext does
|
||||||
|
|
||||||
|
oauth.CredentialData.Data.Remove("authorizationUrlV2");
|
||||||
|
var cred4 = oauth.GetVssCredentials(hc, true); // v2 url is not there
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.Equal("http://myserver/", (cred.Federated as VssOAuthCredential).AuthorizationUrl.AbsoluteUri);
|
||||||
|
Assert.Equal("someClientId", (cred.Federated as VssOAuthCredential).ClientCredential.ClientId);
|
||||||
|
|
||||||
|
Assert.Equal("http://myserver/", (cred2.Federated as VssOAuthCredential).AuthorizationUrl.AbsoluteUri);
|
||||||
|
Assert.Equal("someClientId", (cred2.Federated as VssOAuthCredential).ClientCredential.ClientId);
|
||||||
|
|
||||||
|
Assert.Equal("http://myserver/", (cred3.Federated as VssOAuthCredential).AuthorizationUrl.AbsoluteUri);
|
||||||
|
Assert.Equal("someClientId", (cred3.Federated as VssOAuthCredential).ClientCredential.ClientId);
|
||||||
|
|
||||||
|
Assert.Equal("http://myserver/", (cred4.Federated as VssOAuthCredential).AuthorizationUrl.AbsoluteUri);
|
||||||
|
Assert.Equal("someClientId", (cred4.Federated as VssOAuthCredential).ClientCredential.ClientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "OAuthCredential")]
|
||||||
|
public void UseAuthV2Url()
|
||||||
|
{
|
||||||
|
using (TestHostContext hc = new(this))
|
||||||
|
{
|
||||||
|
// Arrange.
|
||||||
|
var oauth = new OAuthCredential();
|
||||||
|
oauth.CredentialData = new CredentialData()
|
||||||
|
{
|
||||||
|
Scheme = Constants.Configuration.OAuth
|
||||||
|
};
|
||||||
|
oauth.CredentialData.Data.Add("clientId", "someClientId");
|
||||||
|
oauth.CredentialData.Data.Add("authorizationUrl", "http://myserver/");
|
||||||
|
oauth.CredentialData.Data.Add("authorizationUrlV2", "http://myserverv2/");
|
||||||
|
|
||||||
|
_rsaKeyManager.Setup(x => x.GetKey()).Returns(RSA.Create(2048));
|
||||||
|
hc.SetSingleton<IRSAKeyManager>(_rsaKeyManager.Object);
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
hc.EnableAuthMigration("L0Test");
|
||||||
|
var cred = oauth.GetVssCredentials(hc, true);
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.Equal("http://myserverv2/", (cred.Federated as VssOAuthCredential).AuthorizationUrl.AbsoluteUri);
|
||||||
|
Assert.Equal("someClientId", (cred.Federated as VssOAuthCredential).ClientCredential.ClientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,20 +36,23 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_configurationStore = new Mock<IConfigurationStore>();
|
_configurationStore = new Mock<IConfigurationStore>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Pipelines.AgentJobRequestMessage CreateJobRequestMessage()
|
private Pipelines.AgentJobRequestMessage CreateJobRequestMessage(string billingOwnerId = null)
|
||||||
{
|
{
|
||||||
TaskOrchestrationPlanReference plan = new();
|
TaskOrchestrationPlanReference plan = new();
|
||||||
TimelineReference timeline = null;
|
TimelineReference timeline = null;
|
||||||
Guid jobId = Guid.NewGuid();
|
Guid jobId = Guid.NewGuid();
|
||||||
var result = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "someJob", "someJob", null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
|
var result = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, "someJob", "someJob", null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null, null);
|
||||||
result.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
|
result.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
|
||||||
|
result.BillingOwnerId = billingOwnerId;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Theory]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void DispatchesJobRequest()
|
[InlineData(null)]
|
||||||
|
[InlineData("billingOwnerId")]
|
||||||
|
public async void DispatchesJobRequest(string billingOwnerId)
|
||||||
{
|
{
|
||||||
//Arrange
|
//Arrange
|
||||||
using (var hc = new TestHostContext(this))
|
using (var hc = new TestHostContext(this))
|
||||||
@@ -65,7 +68,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
jobDispatcher.Initialize(hc);
|
jobDispatcher.Initialize(hc);
|
||||||
|
|
||||||
var ts = new CancellationTokenSource();
|
var ts = new CancellationTokenSource();
|
||||||
Pipelines.AgentJobRequestMessage message = CreateJobRequestMessage();
|
Pipelines.AgentJobRequestMessage message = CreateJobRequestMessage(billingOwnerId);
|
||||||
string strMessage = JsonUtility.ToString(message);
|
string strMessage = JsonUtility.ToString(message);
|
||||||
|
|
||||||
_processInvoker.Setup(x => x.ExecuteAsync(It.IsAny<String>(), It.IsAny<String>(), "spawnclient 1 2", null, It.IsAny<CancellationToken>()))
|
_processInvoker.Setup(x => x.ExecuteAsync(It.IsAny<String>(), It.IsAny<String>(), "spawnclient 1 2", null, It.IsAny<CancellationToken>()))
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void CreatesSession()
|
public async Task CreatesSession()
|
||||||
{
|
{
|
||||||
using (TestHostContext tc = CreateTestContext())
|
using (TestHostContext tc = CreateTestContext())
|
||||||
using (var tokenSource = new CancellationTokenSource())
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
@@ -67,7 +67,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
tokenSource.Token))
|
tokenSource.Token))
|
||||||
.Returns(Task.FromResult(expectedSession));
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
_credMgr.Setup(x => x.LoadCredentials()).Returns(new VssCredentials());
|
_credMgr.Setup(x => x.LoadCredentials(It.IsAny<bool>())).Returns(new VssCredentials());
|
||||||
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
||||||
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
||||||
|
|
||||||
@@ -95,69 +95,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void CreatesSessionWithBrokerMigration()
|
public async Task DeleteSession()
|
||||||
{
|
|
||||||
using (TestHostContext tc = CreateTestContext())
|
|
||||||
using (var tokenSource = new CancellationTokenSource())
|
|
||||||
{
|
|
||||||
Tracing trace = tc.GetTrace();
|
|
||||||
|
|
||||||
// Arrange.
|
|
||||||
var expectedSession = new TaskAgentSession()
|
|
||||||
{
|
|
||||||
OwnerName = "legacy",
|
|
||||||
BrokerMigrationMessage = new BrokerMigrationMessage(new Uri("https://broker.actions.github.com"))
|
|
||||||
};
|
|
||||||
|
|
||||||
var expectedBrokerSession = new TaskAgentSession()
|
|
||||||
{
|
|
||||||
OwnerName = "broker"
|
|
||||||
};
|
|
||||||
|
|
||||||
_runnerServer
|
|
||||||
.Setup(x => x.CreateAgentSessionAsync(
|
|
||||||
_settings.PoolId,
|
|
||||||
It.Is<TaskAgentSession>(y => y != null),
|
|
||||||
tokenSource.Token))
|
|
||||||
.Returns(Task.FromResult(expectedSession));
|
|
||||||
|
|
||||||
_brokerServer
|
|
||||||
.Setup(x => x.CreateSessionAsync(
|
|
||||||
It.Is<TaskAgentSession>(y => y != null),
|
|
||||||
tokenSource.Token))
|
|
||||||
.Returns(Task.FromResult(expectedBrokerSession));
|
|
||||||
|
|
||||||
_credMgr.Setup(x => x.LoadCredentials()).Returns(new VssCredentials());
|
|
||||||
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
|
||||||
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
|
||||||
|
|
||||||
// Act.
|
|
||||||
MessageListener listener = new();
|
|
||||||
listener.Initialize(tc);
|
|
||||||
|
|
||||||
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
|
||||||
trace.Info("result: {0}", result);
|
|
||||||
|
|
||||||
// Assert.
|
|
||||||
Assert.Equal(CreateSessionResult.Success, result);
|
|
||||||
|
|
||||||
_runnerServer
|
|
||||||
.Verify(x => x.CreateAgentSessionAsync(
|
|
||||||
_settings.PoolId,
|
|
||||||
It.Is<TaskAgentSession>(y => y != null),
|
|
||||||
tokenSource.Token), Times.Once());
|
|
||||||
|
|
||||||
_brokerServer
|
|
||||||
.Verify(x => x.CreateSessionAsync(
|
|
||||||
It.Is<TaskAgentSession>(y => y != null),
|
|
||||||
tokenSource.Token), Times.Once());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Runner")]
|
|
||||||
public async void DeleteSession()
|
|
||||||
{
|
{
|
||||||
using (TestHostContext tc = CreateTestContext())
|
using (TestHostContext tc = CreateTestContext())
|
||||||
using (var tokenSource = new CancellationTokenSource())
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
@@ -177,7 +115,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
tokenSource.Token))
|
tokenSource.Token))
|
||||||
.Returns(Task.FromResult(expectedSession));
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
_credMgr.Setup(x => x.LoadCredentials()).Returns(new VssCredentials());
|
_credMgr.Setup(x => x.LoadCredentials(It.IsAny<bool>())).Returns(new VssCredentials());
|
||||||
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
||||||
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
||||||
|
|
||||||
@@ -204,84 +142,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void DeleteSessionWithBrokerMigration()
|
public async Task GetNextMessage()
|
||||||
{
|
|
||||||
using (TestHostContext tc = CreateTestContext())
|
|
||||||
using (var tokenSource = new CancellationTokenSource())
|
|
||||||
{
|
|
||||||
Tracing trace = tc.GetTrace();
|
|
||||||
|
|
||||||
// Arrange.
|
|
||||||
var expectedSession = new TaskAgentSession()
|
|
||||||
{
|
|
||||||
OwnerName = "legacy",
|
|
||||||
BrokerMigrationMessage = new BrokerMigrationMessage(new Uri("https://broker.actions.github.com"))
|
|
||||||
};
|
|
||||||
|
|
||||||
var expectedBrokerSession = new TaskAgentSession()
|
|
||||||
{
|
|
||||||
SessionId = Guid.NewGuid(),
|
|
||||||
OwnerName = "broker"
|
|
||||||
};
|
|
||||||
|
|
||||||
_runnerServer
|
|
||||||
.Setup(x => x.CreateAgentSessionAsync(
|
|
||||||
_settings.PoolId,
|
|
||||||
It.Is<TaskAgentSession>(y => y != null),
|
|
||||||
tokenSource.Token))
|
|
||||||
.Returns(Task.FromResult(expectedSession));
|
|
||||||
|
|
||||||
_brokerServer
|
|
||||||
.Setup(x => x.CreateSessionAsync(
|
|
||||||
It.Is<TaskAgentSession>(y => y != null),
|
|
||||||
tokenSource.Token))
|
|
||||||
.Returns(Task.FromResult(expectedBrokerSession));
|
|
||||||
|
|
||||||
_credMgr.Setup(x => x.LoadCredentials()).Returns(new VssCredentials());
|
|
||||||
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
|
||||||
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
|
||||||
|
|
||||||
// Act.
|
|
||||||
MessageListener listener = new();
|
|
||||||
listener.Initialize(tc);
|
|
||||||
|
|
||||||
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
|
||||||
trace.Info("result: {0}", result);
|
|
||||||
|
|
||||||
Assert.Equal(CreateSessionResult.Success, result);
|
|
||||||
|
|
||||||
_runnerServer
|
|
||||||
.Verify(x => x.CreateAgentSessionAsync(
|
|
||||||
_settings.PoolId,
|
|
||||||
It.Is<TaskAgentSession>(y => y != null),
|
|
||||||
tokenSource.Token), Times.Once());
|
|
||||||
|
|
||||||
_brokerServer
|
|
||||||
.Verify(x => x.CreateSessionAsync(
|
|
||||||
It.Is<TaskAgentSession>(y => y != null),
|
|
||||||
tokenSource.Token), Times.Once());
|
|
||||||
|
|
||||||
_brokerServer
|
|
||||||
.Setup(x => x.DeleteSessionAsync(It.IsAny<CancellationToken>()))
|
|
||||||
.Returns(Task.CompletedTask);
|
|
||||||
|
|
||||||
// Act.
|
|
||||||
await listener.DeleteSessionAsync();
|
|
||||||
|
|
||||||
|
|
||||||
//Assert
|
|
||||||
_runnerServer
|
|
||||||
.Verify(x => x.DeleteAgentSessionAsync(
|
|
||||||
_settings.PoolId, expectedBrokerSession.SessionId, It.IsAny<CancellationToken>()), Times.Once());
|
|
||||||
_brokerServer
|
|
||||||
.Verify(x => x.DeleteSessionAsync(It.IsAny<CancellationToken>()), Times.Once());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Runner")]
|
|
||||||
public async void GetNextMessage()
|
|
||||||
{
|
{
|
||||||
using (TestHostContext tc = CreateTestContext())
|
using (TestHostContext tc = CreateTestContext())
|
||||||
using (var tokenSource = new CancellationTokenSource())
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
@@ -301,7 +162,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
tokenSource.Token))
|
tokenSource.Token))
|
||||||
.Returns(Task.FromResult(expectedSession));
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
_credMgr.Setup(x => x.LoadCredentials()).Returns(new VssCredentials());
|
_credMgr.Setup(x => x.LoadCredentials(It.IsAny<bool>())).Returns(new VssCredentials());
|
||||||
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
||||||
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
||||||
|
|
||||||
@@ -362,7 +223,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void GetNextMessageWithBrokerMigration()
|
public async Task GetNextMessageWithBrokerMigration()
|
||||||
{
|
{
|
||||||
using (TestHostContext tc = CreateTestContext())
|
using (TestHostContext tc = CreateTestContext())
|
||||||
using (var tokenSource = new CancellationTokenSource())
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
@@ -382,7 +243,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
tokenSource.Token))
|
tokenSource.Token))
|
||||||
.Returns(Task.FromResult(expectedSession));
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
_credMgr.Setup(x => x.LoadCredentials()).Returns(new VssCredentials());
|
_credMgr.Setup(x => x.LoadCredentials(It.IsAny<bool>())).Returns(new VssCredentials());
|
||||||
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
||||||
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
||||||
|
|
||||||
@@ -462,13 +323,22 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_brokerServer
|
_brokerServer
|
||||||
.Verify(x => x.GetRunnerMessageAsync(
|
.Verify(x => x.GetRunnerMessageAsync(
|
||||||
expectedSession.SessionId, TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Exactly(brokerMessages.Length));
|
expectedSession.SessionId, TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Exactly(brokerMessages.Length));
|
||||||
|
|
||||||
|
_credMgr
|
||||||
|
.Verify(x => x.LoadCredentials(true), Times.Exactly(brokerMessages.Length));
|
||||||
|
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.UpdateConnectionIfNeeded(brokerMigrationMesage.BrokerBaseUrl, It.IsAny<VssCredentials>()), Times.Exactly(brokerMessages.Length));
|
||||||
|
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.ForceRefreshConnection(It.IsAny<VssCredentials>()), Times.Never);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void CreateSessionWithOriginalCredential()
|
public async Task CreateSessionWithOriginalCredential()
|
||||||
{
|
{
|
||||||
using (TestHostContext tc = CreateTestContext())
|
using (TestHostContext tc = CreateTestContext())
|
||||||
using (var tokenSource = new CancellationTokenSource())
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
@@ -484,7 +354,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
tokenSource.Token))
|
tokenSource.Token))
|
||||||
.Returns(Task.FromResult(expectedSession));
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
_credMgr.Setup(x => x.LoadCredentials()).Returns(new VssCredentials());
|
_credMgr.Setup(x => x.LoadCredentials(It.IsAny<bool>())).Returns(new VssCredentials());
|
||||||
|
|
||||||
var originalCred = new CredentialData() { Scheme = Constants.Configuration.OAuth };
|
var originalCred = new CredentialData() { Scheme = Constants.Configuration.OAuth };
|
||||||
originalCred.Data["authorizationUrl"] = "https://s.server";
|
originalCred.Data["authorizationUrl"] = "https://s.server";
|
||||||
@@ -513,7 +383,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void SkipDeleteSession_WhenGetNextMessageGetTaskAgentAccessTokenExpiredException()
|
public async Task SkipDeleteSession_WhenGetNextMessageGetTaskAgentAccessTokenExpiredException()
|
||||||
{
|
{
|
||||||
using (TestHostContext tc = CreateTestContext())
|
using (TestHostContext tc = CreateTestContext())
|
||||||
using (var tokenSource = new CancellationTokenSource())
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
@@ -533,7 +403,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
tokenSource.Token))
|
tokenSource.Token))
|
||||||
.Returns(Task.FromResult(expectedSession));
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
_credMgr.Setup(x => x.LoadCredentials()).Returns(new VssCredentials());
|
_credMgr.Setup(x => x.LoadCredentials(It.IsAny<bool>())).Returns(new VssCredentials());
|
||||||
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
||||||
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
||||||
|
|
||||||
@@ -571,5 +441,301 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_settings.PoolId, expectedSession.SessionId, It.IsAny<CancellationToken>()), Times.Never);
|
_settings.PoolId, expectedSession.SessionId, It.IsAny<CancellationToken>()), Times.Never);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task HandleAuthMigrationChanged()
|
||||||
|
{
|
||||||
|
using (TestHostContext tc = CreateTestContext())
|
||||||
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
|
{
|
||||||
|
Tracing trace = tc.GetTrace();
|
||||||
|
|
||||||
|
// Arrange.
|
||||||
|
var expectedSession = new TaskAgentSession();
|
||||||
|
_runnerServer
|
||||||
|
.Setup(x => x.CreateAgentSessionAsync(
|
||||||
|
_settings.PoolId,
|
||||||
|
It.Is<TaskAgentSession>(y => y != null),
|
||||||
|
tokenSource.Token))
|
||||||
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
|
_credMgr.Setup(x => x.LoadCredentials(It.IsAny<bool>())).Returns(new VssCredentials());
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
MessageListener listener = new();
|
||||||
|
listener.Initialize(tc);
|
||||||
|
|
||||||
|
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||||
|
trace.Info("result: {0}", result);
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.Equal(CreateSessionResult.Success, result);
|
||||||
|
_runnerServer
|
||||||
|
.Verify(x => x.CreateAgentSessionAsync(
|
||||||
|
_settings.PoolId,
|
||||||
|
It.Is<TaskAgentSession>(y => y != null),
|
||||||
|
tokenSource.Token), Times.Once());
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.CreateSessionAsync(
|
||||||
|
It.Is<TaskAgentSession>(y => y != null),
|
||||||
|
tokenSource.Token), Times.Never());
|
||||||
|
|
||||||
|
tc.EnableAuthMigration("L0Test");
|
||||||
|
|
||||||
|
var traceFile = Path.GetTempFileName();
|
||||||
|
File.Copy(tc.TraceFileName, traceFile, true);
|
||||||
|
Assert.Contains("Auth migration changed", File.ReadAllText(traceFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task GetNextMessageWithBrokerMigration_AuthMigrationFallback()
|
||||||
|
{
|
||||||
|
using (TestHostContext tc = CreateTestContext())
|
||||||
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
|
{
|
||||||
|
Tracing trace = tc.GetTrace();
|
||||||
|
|
||||||
|
// Arrange.
|
||||||
|
var expectedSession = new TaskAgentSession();
|
||||||
|
PropertyInfo sessionIdProperty = expectedSession.GetType().GetProperty("SessionId", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
|
Assert.NotNull(sessionIdProperty);
|
||||||
|
sessionIdProperty.SetValue(expectedSession, Guid.NewGuid());
|
||||||
|
|
||||||
|
_runnerServer
|
||||||
|
.Setup(x => x.CreateAgentSessionAsync(
|
||||||
|
_settings.PoolId,
|
||||||
|
It.Is<TaskAgentSession>(y => y != null),
|
||||||
|
tokenSource.Token))
|
||||||
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
|
_credMgr.Setup(x => x.LoadCredentials(It.IsAny<bool>())).Returns(new VssCredentials());
|
||||||
|
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
||||||
|
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
MessageListener listener = new();
|
||||||
|
listener.Initialize(tc);
|
||||||
|
|
||||||
|
tc.EnableAuthMigration("L0Test");
|
||||||
|
|
||||||
|
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||||
|
Assert.Equal(CreateSessionResult.Success, result);
|
||||||
|
|
||||||
|
var brokerMigrationMesage = new BrokerMigrationMessage(new Uri("https://actions.broker.com"));
|
||||||
|
|
||||||
|
var arMessages = new TaskAgentMessage[]
|
||||||
|
{
|
||||||
|
new TaskAgentMessage
|
||||||
|
{
|
||||||
|
Body = JsonUtility.ToString(brokerMigrationMesage),
|
||||||
|
MessageType = BrokerMigrationMessage.MessageType
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var brokerMessages = new TaskAgentMessage[]
|
||||||
|
{
|
||||||
|
new TaskAgentMessage
|
||||||
|
{
|
||||||
|
Body = "somebody1",
|
||||||
|
MessageId = 4234,
|
||||||
|
MessageType = JobRequestMessageTypes.PipelineAgentJobRequest
|
||||||
|
},
|
||||||
|
new TaskAgentMessage
|
||||||
|
{
|
||||||
|
Body = "somebody2",
|
||||||
|
MessageId = 4235,
|
||||||
|
MessageType = JobCancelMessage.MessageType
|
||||||
|
},
|
||||||
|
null, //should be skipped by GetNextMessageAsync implementation
|
||||||
|
null,
|
||||||
|
new TaskAgentMessage
|
||||||
|
{
|
||||||
|
Body = "somebody3",
|
||||||
|
MessageId = 4236,
|
||||||
|
MessageType = JobRequestMessageTypes.PipelineAgentJobRequest
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var brokerMessageQueue = new Queue<TaskAgentMessage>(brokerMessages);
|
||||||
|
|
||||||
|
_runnerServer
|
||||||
|
.Setup(x => x.GetAgentMessageAsync(
|
||||||
|
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(async (Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, string runnerVersion, string os, string architecture, bool disableUpdate, CancellationToken cancellationToken) =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
return arMessages[0]; // always send migration message
|
||||||
|
});
|
||||||
|
|
||||||
|
var counter = 0;
|
||||||
|
_brokerServer
|
||||||
|
.Setup(x => x.GetRunnerMessageAsync(
|
||||||
|
expectedSession.SessionId, TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(async (Guid sessionId, TaskAgentStatus status, string runnerVersion, string os, string architecture, bool disableUpdate, CancellationToken cancellationToken) =>
|
||||||
|
{
|
||||||
|
counter++;
|
||||||
|
await Task.Yield();
|
||||||
|
if (counter == 2)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Something wrong.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return brokerMessageQueue.Dequeue();
|
||||||
|
});
|
||||||
|
|
||||||
|
TaskAgentMessage message1 = await listener.GetNextMessageAsync(tokenSource.Token);
|
||||||
|
TaskAgentMessage message2 = await listener.GetNextMessageAsync(tokenSource.Token);
|
||||||
|
TaskAgentMessage message3 = await listener.GetNextMessageAsync(tokenSource.Token);
|
||||||
|
Assert.Equal(brokerMessages[0], message1);
|
||||||
|
Assert.Equal(brokerMessages[1], message2);
|
||||||
|
Assert.Equal(brokerMessages[4], message3);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
_runnerServer
|
||||||
|
.Verify(x => x.GetAgentMessageAsync(
|
||||||
|
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Exactly(brokerMessages.Length + 1));
|
||||||
|
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.GetRunnerMessageAsync(
|
||||||
|
expectedSession.SessionId, TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Exactly(brokerMessages.Length + 1));
|
||||||
|
|
||||||
|
_credMgr
|
||||||
|
.Verify(x => x.LoadCredentials(true), Times.Exactly(brokerMessages.Length + 1));
|
||||||
|
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.UpdateConnectionIfNeeded(brokerMigrationMesage.BrokerBaseUrl, It.IsAny<VssCredentials>()), Times.Exactly(brokerMessages.Length + 1));
|
||||||
|
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.ForceRefreshConnection(It.IsAny<VssCredentials>()), Times.Once());
|
||||||
|
|
||||||
|
Assert.False(tc.AllowAuthMigration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task GetNextMessageWithBrokerMigration_EnableAuthMigration()
|
||||||
|
{
|
||||||
|
using (TestHostContext tc = CreateTestContext())
|
||||||
|
using (var tokenSource = new CancellationTokenSource())
|
||||||
|
{
|
||||||
|
Tracing trace = tc.GetTrace();
|
||||||
|
|
||||||
|
// Arrange.
|
||||||
|
var expectedSession = new TaskAgentSession();
|
||||||
|
PropertyInfo sessionIdProperty = expectedSession.GetType().GetProperty("SessionId", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
|
Assert.NotNull(sessionIdProperty);
|
||||||
|
sessionIdProperty.SetValue(expectedSession, Guid.NewGuid());
|
||||||
|
|
||||||
|
_runnerServer
|
||||||
|
.Setup(x => x.CreateAgentSessionAsync(
|
||||||
|
_settings.PoolId,
|
||||||
|
It.Is<TaskAgentSession>(y => y != null),
|
||||||
|
tokenSource.Token))
|
||||||
|
.Returns(Task.FromResult(expectedSession));
|
||||||
|
|
||||||
|
_credMgr.Setup(x => x.LoadCredentials(It.IsAny<bool>())).Returns(new VssCredentials());
|
||||||
|
_store.Setup(x => x.GetCredentials()).Returns(new CredentialData() { Scheme = Constants.Configuration.OAuthAccessToken });
|
||||||
|
_store.Setup(x => x.GetMigratedCredentials()).Returns(default(CredentialData));
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
MessageListener listener = new();
|
||||||
|
listener.Initialize(tc);
|
||||||
|
|
||||||
|
CreateSessionResult result = await listener.CreateSessionAsync(tokenSource.Token);
|
||||||
|
Assert.Equal(CreateSessionResult.Success, result);
|
||||||
|
|
||||||
|
var brokerMigrationMesage = new BrokerMigrationMessage(new Uri("https://actions.broker.com"));
|
||||||
|
|
||||||
|
var arMessages = new TaskAgentMessage[]
|
||||||
|
{
|
||||||
|
new TaskAgentMessage
|
||||||
|
{
|
||||||
|
Body = JsonUtility.ToString(brokerMigrationMesage),
|
||||||
|
MessageType = BrokerMigrationMessage.MessageType
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var brokerMessages = new TaskAgentMessage[]
|
||||||
|
{
|
||||||
|
new TaskAgentMessage
|
||||||
|
{
|
||||||
|
Body = "somebody1",
|
||||||
|
MessageId = 4234,
|
||||||
|
MessageType = JobRequestMessageTypes.PipelineAgentJobRequest
|
||||||
|
},
|
||||||
|
new TaskAgentMessage
|
||||||
|
{
|
||||||
|
Body = "somebody2",
|
||||||
|
MessageId = 4235,
|
||||||
|
MessageType = JobCancelMessage.MessageType
|
||||||
|
},
|
||||||
|
null, //should be skipped by GetNextMessageAsync implementation
|
||||||
|
null,
|
||||||
|
new TaskAgentMessage
|
||||||
|
{
|
||||||
|
Body = "somebody3",
|
||||||
|
MessageId = 4236,
|
||||||
|
MessageType = JobRequestMessageTypes.PipelineAgentJobRequest
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var brokerMessageQueue = new Queue<TaskAgentMessage>(brokerMessages);
|
||||||
|
|
||||||
|
_runnerServer
|
||||||
|
.Setup(x => x.GetAgentMessageAsync(
|
||||||
|
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(async (Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, string runnerVersion, string os, string architecture, bool disableUpdate, CancellationToken cancellationToken) =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
return arMessages[0]; // always send migration message
|
||||||
|
});
|
||||||
|
|
||||||
|
_brokerServer
|
||||||
|
.Setup(x => x.GetRunnerMessageAsync(
|
||||||
|
expectedSession.SessionId, TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(async (Guid sessionId, TaskAgentStatus status, string runnerVersion, string os, string architecture, bool disableUpdate, CancellationToken cancellationToken) =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
if (!tc.AllowAuthMigration)
|
||||||
|
{
|
||||||
|
tc.EnableAuthMigration("L0Test");
|
||||||
|
}
|
||||||
|
|
||||||
|
return brokerMessageQueue.Dequeue();
|
||||||
|
});
|
||||||
|
|
||||||
|
TaskAgentMessage message1 = await listener.GetNextMessageAsync(tokenSource.Token);
|
||||||
|
TaskAgentMessage message2 = await listener.GetNextMessageAsync(tokenSource.Token);
|
||||||
|
TaskAgentMessage message3 = await listener.GetNextMessageAsync(tokenSource.Token);
|
||||||
|
Assert.Equal(brokerMessages[0], message1);
|
||||||
|
Assert.Equal(brokerMessages[1], message2);
|
||||||
|
Assert.Equal(brokerMessages[4], message3);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
_runnerServer
|
||||||
|
.Verify(x => x.GetAgentMessageAsync(
|
||||||
|
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Exactly(brokerMessages.Length));
|
||||||
|
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.GetRunnerMessageAsync(
|
||||||
|
expectedSession.SessionId, TaskAgentStatus.Online, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Exactly(brokerMessages.Length));
|
||||||
|
|
||||||
|
_credMgr
|
||||||
|
.Verify(x => x.LoadCredentials(true), Times.Exactly(brokerMessages.Length));
|
||||||
|
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.UpdateConnectionIfNeeded(brokerMigrationMesage.BrokerBaseUrl, It.IsAny<VssCredentials>()), Times.Exactly(brokerMessages.Length));
|
||||||
|
|
||||||
|
_brokerServer
|
||||||
|
.Verify(x => x.ForceRefreshConnection(It.IsAny<VssCredentials>()), Times.Once());
|
||||||
|
|
||||||
|
Assert.True(tc.AllowAuthMigration);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
678
src/Test/L0/Listener/RunnerConfigUpdaterTests.cs
Normal file
678
src/Test/L0/Listener/RunnerConfigUpdaterTests.cs
Normal file
@@ -0,0 +1,678 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Common.Tests;
|
||||||
|
using GitHub.Runner.Listener;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Tests.Listener
|
||||||
|
{
|
||||||
|
public class RunnerConfigUpdaterL0
|
||||||
|
{
|
||||||
|
private Mock<IConfigurationStore> _configurationStore;
|
||||||
|
private Mock<IRunnerServer> _runnerServer;
|
||||||
|
|
||||||
|
public RunnerConfigUpdaterL0()
|
||||||
|
{
|
||||||
|
_configurationStore = new Mock<IConfigurationStore>();
|
||||||
|
_runnerServer = new Mock<IRunnerServer>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task UpdateRunnerConfigAsync_InvalidRunnerQualifiedId_ShouldReportTelemetry()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var setting = new RunnerSettings { AgentId = 1, AgentName = "agent1" };
|
||||||
|
_configurationStore.Setup(x => x.GetSettings()).Returns(setting);
|
||||||
|
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
|
||||||
|
|
||||||
|
var _runnerConfigUpdater = new RunnerConfigUpdater();
|
||||||
|
_runnerConfigUpdater.Initialize(hc);
|
||||||
|
|
||||||
|
var invalidRunnerQualifiedId = "invalid/runner/qualified/id";
|
||||||
|
var configType = "runner";
|
||||||
|
var serviceType = "pipelines";
|
||||||
|
var configRefreshUrl = "http://example.com";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _runnerConfigUpdater.UpdateRunnerConfigAsync(invalidRunnerQualifiedId, configType, serviceType, configRefreshUrl);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>((s) => s.Contains("Runner qualified id")), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task UpdateRunnerConfigAsync_ValidRunnerQualifiedId_ShouldNotReportTelemetry()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var setting = new RunnerSettings { AgentId = 1, AgentName = "agent1" };
|
||||||
|
_configurationStore.Setup(x => x.GetSettings()).Returns(setting);
|
||||||
|
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
|
||||||
|
|
||||||
|
var _runnerConfigUpdater = new RunnerConfigUpdater();
|
||||||
|
_runnerConfigUpdater.Initialize(hc);
|
||||||
|
|
||||||
|
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
|
||||||
|
var configType = "runner";
|
||||||
|
var serviceType = "pipelines";
|
||||||
|
var configRefreshUrl = "http://example.com";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _runnerConfigUpdater.UpdateRunnerConfigAsync(validRunnerQualifiedId, configType, serviceType, configRefreshUrl);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>((s) => s.Contains("Runner qualified id")), It.IsAny<CancellationToken>()), Times.Never);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task UpdateRunnerConfigAsync_InvalidConfigType_ShouldReportTelemetry()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var setting = new RunnerSettings { AgentId = 1, AgentName = "agent1" };
|
||||||
|
_configurationStore.Setup(x => x.GetSettings()).Returns(setting);
|
||||||
|
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
|
||||||
|
|
||||||
|
var _runnerConfigUpdater = new RunnerConfigUpdater();
|
||||||
|
_runnerConfigUpdater.Initialize(hc);
|
||||||
|
|
||||||
|
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
|
||||||
|
var invalidConfigType = "invalidConfigType";
|
||||||
|
var serviceType = "pipelines";
|
||||||
|
var configRefreshUrl = "http://example.com";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _runnerConfigUpdater.UpdateRunnerConfigAsync(validRunnerQualifiedId, invalidConfigType, serviceType, configRefreshUrl);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>((s) => s.Contains("Invalid config type")), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task UpdateRunnerConfigAsync_UpdateRunnerSettings_ShouldSucceed()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var setting = new RunnerSettings { AgentId = 1, AgentName = "agent1" };
|
||||||
|
_configurationStore.Setup(x => x.GetSettings()).Returns(setting);
|
||||||
|
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
|
||||||
|
|
||||||
|
var encodedConfig = Convert.ToBase64String(Encoding.UTF8.GetBytes(StringUtil.ConvertToJson(setting)));
|
||||||
|
_runnerServer.Setup(x => x.RefreshRunnerConfigAsync(It.IsAny<int>(), It.Is<string>(s => s == "runner"), It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(encodedConfig);
|
||||||
|
|
||||||
|
var _runnerConfigUpdater = new RunnerConfigUpdater();
|
||||||
|
_runnerConfigUpdater.Initialize(hc);
|
||||||
|
|
||||||
|
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
|
||||||
|
var configType = "runner";
|
||||||
|
var serviceType = "pipelines";
|
||||||
|
var configRefreshUrl = "http://example.com";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _runnerConfigUpdater.UpdateRunnerConfigAsync(validRunnerQualifiedId, configType, serviceType, configRefreshUrl);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_runnerServer.Verify(x => x.RefreshRunnerConfigAsync(1, "runner", It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>(s => s.Contains("Runner settings updated successfully")), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_configurationStore.Verify(x => x.SaveMigratedSettings(It.IsAny<RunnerSettings>()), Times.Once);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task UpdateRunnerConfigAsync_UpdateRunnerSettings_IgnoredEmptyRefreshResult()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var setting = new RunnerSettings { AgentId = 1, AgentName = "agent1" };
|
||||||
|
_configurationStore.Setup(x => x.GetSettings()).Returns(setting);
|
||||||
|
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
|
||||||
|
|
||||||
|
var _runnerConfigUpdater = new RunnerConfigUpdater();
|
||||||
|
_runnerConfigUpdater.Initialize(hc);
|
||||||
|
|
||||||
|
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
|
||||||
|
var configType = "runner";
|
||||||
|
var serviceType = "pipelines";
|
||||||
|
var configRefreshUrl = "http://example.com";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _runnerConfigUpdater.UpdateRunnerConfigAsync(validRunnerQualifiedId, configType, serviceType, configRefreshUrl);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_runnerServer.Verify(x => x.RefreshRunnerConfigAsync(1, "runner", It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>(s => s.Contains("Runner settings updated successfully")), It.IsAny<CancellationToken>()), Times.Never);
|
||||||
|
_configurationStore.Verify(x => x.SaveMigratedSettings(It.IsAny<RunnerSettings>()), Times.Never);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task UpdateRunnerConfigAsync_UpdateRunnerCredentials_ShouldSucceed()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var setting = new RunnerSettings { AgentId = 1, AgentName = "agent1" };
|
||||||
|
_configurationStore.Setup(x => x.GetSettings()).Returns(setting);
|
||||||
|
var credData = new CredentialData
|
||||||
|
{
|
||||||
|
Scheme = "OAuth"
|
||||||
|
};
|
||||||
|
credData.Data.Add("ClientId", "12345");
|
||||||
|
_configurationStore.Setup(x => x.GetCredentials()).Returns(credData);
|
||||||
|
|
||||||
|
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
|
||||||
|
IOUtil.SaveObject(credData, hc.GetConfigFile(WellKnownConfigFile.Credentials));
|
||||||
|
|
||||||
|
var encodedConfig = Convert.ToBase64String(Encoding.UTF8.GetBytes(StringUtil.ConvertToJson(credData)));
|
||||||
|
_runnerServer.Setup(x => x.RefreshRunnerConfigAsync(It.IsAny<int>(), It.Is<string>(s => s == "credentials"), It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(encodedConfig);
|
||||||
|
|
||||||
|
var _runnerConfigUpdater = new RunnerConfigUpdater();
|
||||||
|
_runnerConfigUpdater.Initialize(hc);
|
||||||
|
hc.EnableAuthMigration("L0Test");
|
||||||
|
|
||||||
|
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
|
||||||
|
var configType = "credentials";
|
||||||
|
var serviceType = "pipelines";
|
||||||
|
var configRefreshUrl = "http://example.com";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _runnerConfigUpdater.UpdateRunnerConfigAsync(validRunnerQualifiedId, configType, serviceType, configRefreshUrl);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_runnerServer.Verify(x => x.RefreshRunnerConfigAsync(1, "credentials", It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>(s => s.Contains("Runner credentials updated successfully")), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_configurationStore.Verify(x => x.SaveMigratedCredential(It.IsAny<CredentialData>()), Times.Once);
|
||||||
|
Assert.False(hc.AllowAuthMigration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task UpdateRunnerConfigAsync_UpdateRunnerCredentials_IgnoredEmptyRefreshResult()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var setting = new RunnerSettings { AgentId = 1, AgentName = "agent1" };
|
||||||
|
_configurationStore.Setup(x => x.GetSettings()).Returns(setting);
|
||||||
|
var credData = new CredentialData
|
||||||
|
{
|
||||||
|
Scheme = "OAuth"
|
||||||
|
};
|
||||||
|
credData.Data.Add("ClientId", "12345");
|
||||||
|
_configurationStore.Setup(x => x.GetCredentials()).Returns(credData);
|
||||||
|
|
||||||
|
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
|
||||||
|
IOUtil.SaveObject(credData, hc.GetConfigFile(WellKnownConfigFile.Credentials));
|
||||||
|
|
||||||
|
var _runnerConfigUpdater = new RunnerConfigUpdater();
|
||||||
|
_runnerConfigUpdater.Initialize(hc);
|
||||||
|
|
||||||
|
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
|
||||||
|
var configType = "credentials";
|
||||||
|
var serviceType = "pipelines";
|
||||||
|
var configRefreshUrl = "http://example.com";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _runnerConfigUpdater.UpdateRunnerConfigAsync(validRunnerQualifiedId, configType, serviceType, configRefreshUrl);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_runnerServer.Verify(x => x.RefreshRunnerConfigAsync(1, "credentials", It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>(s => s.Contains("Runner credentials updated successfully")), It.IsAny<CancellationToken>()), Times.Never);
|
||||||
|
_configurationStore.Verify(x => x.SaveMigratedCredential(It.IsAny<CredentialData>()), Times.Never);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task UpdateRunnerConfigAsync_RefreshRunnerSettingsFailure_ShouldReportTelemetry()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var setting = new RunnerSettings { AgentId = 1, AgentName = "agent1" };
|
||||||
|
_configurationStore.Setup(x => x.GetSettings()).Returns(setting);
|
||||||
|
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
|
||||||
|
_runnerServer.Setup(x => x.RefreshRunnerConfigAsync(It.IsAny<int>(), It.Is<string>(s => s == "runner"), It.IsAny<string>(), It.IsAny<CancellationToken>())).ThrowsAsync(new Exception("Refresh failed"));
|
||||||
|
|
||||||
|
var _runnerConfigUpdater = new RunnerConfigUpdater();
|
||||||
|
_runnerConfigUpdater.Initialize(hc);
|
||||||
|
|
||||||
|
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
|
||||||
|
var configType = "runner";
|
||||||
|
var serviceType = "pipelines";
|
||||||
|
var configRefreshUrl = "http://example.com";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _runnerConfigUpdater.UpdateRunnerConfigAsync(validRunnerQualifiedId, configType, serviceType, configRefreshUrl);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>((s) => s.Contains("Failed to refresh")), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_configurationStore.Verify(x => x.SaveMigratedSettings(It.IsAny<RunnerSettings>()), Times.Never);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task UpdateRunnerConfigAsync_RefreshRunnerCredentialsFailure_ShouldReportTelemetry()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var setting = new RunnerSettings { AgentId = 1, AgentName = "agent1" };
|
||||||
|
_configurationStore.Setup(x => x.GetSettings()).Returns(setting);
|
||||||
|
var credData = new CredentialData
|
||||||
|
{
|
||||||
|
Scheme = "OAuth"
|
||||||
|
};
|
||||||
|
credData.Data.Add("ClientId", "12345");
|
||||||
|
_configurationStore.Setup(x => x.GetCredentials()).Returns(credData);
|
||||||
|
|
||||||
|
_runnerServer.Setup(x => x.RefreshRunnerConfigAsync(It.IsAny<int>(), It.Is<string>(s => s == "credentials"), It.IsAny<string>(), It.IsAny<CancellationToken>())).ThrowsAsync(new Exception("Refresh failed"));
|
||||||
|
|
||||||
|
var _runnerConfigUpdater = new RunnerConfigUpdater();
|
||||||
|
_runnerConfigUpdater.Initialize(hc);
|
||||||
|
|
||||||
|
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
|
||||||
|
var configType = "credentials";
|
||||||
|
var serviceType = "pipelines";
|
||||||
|
var configRefreshUrl = "http://example.com";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _runnerConfigUpdater.UpdateRunnerConfigAsync(validRunnerQualifiedId, configType, serviceType, configRefreshUrl);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>((s) => s.Contains("Failed to refresh")), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_configurationStore.Verify(x => x.SaveMigratedSettings(It.IsAny<RunnerSettings>()), Times.Never);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task UpdateRunnerConfigAsync_RefreshRunnerSettingsWithDifferentRunnerId_ShouldReportTelemetry()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var setting = new RunnerSettings { AgentId = 1, AgentName = "agent1" };
|
||||||
|
_configurationStore.Setup(x => x.GetSettings()).Returns(setting);
|
||||||
|
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
|
||||||
|
|
||||||
|
var differentRunnerSetting = new RunnerSettings { AgentId = 2, AgentName = "agent1" };
|
||||||
|
var encodedConfig = Convert.ToBase64String(Encoding.UTF8.GetBytes(StringUtil.ConvertToJson(differentRunnerSetting)));
|
||||||
|
_runnerServer.Setup(x => x.RefreshRunnerConfigAsync(It.IsAny<int>(), It.Is<string>(s => s == "runner"), It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(encodedConfig);
|
||||||
|
|
||||||
|
var _runnerConfigUpdater = new RunnerConfigUpdater();
|
||||||
|
_runnerConfigUpdater.Initialize(hc);
|
||||||
|
|
||||||
|
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
|
||||||
|
var configType = "runner";
|
||||||
|
var serviceType = "pipelines";
|
||||||
|
var configRefreshUrl = "http://example.com";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _runnerConfigUpdater.UpdateRunnerConfigAsync(validRunnerQualifiedId, configType, serviceType, configRefreshUrl);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>(s => s.Contains("Runner id in refreshed config")), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_configurationStore.Verify(x => x.SaveMigratedSettings(It.IsAny<RunnerSettings>()), Times.Never);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task UpdateRunnerConfigAsync_RefreshRunnerSettingsWithDifferentRunnerName_ShouldReportTelemetry()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var setting = new RunnerSettings { AgentId = 1, AgentName = "agent1" };
|
||||||
|
_configurationStore.Setup(x => x.GetSettings()).Returns(setting);
|
||||||
|
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
|
||||||
|
|
||||||
|
var differentRunnerSetting = new RunnerSettings { AgentId = 1, AgentName = "agent2" };
|
||||||
|
var encodedConfig = Convert.ToBase64String(Encoding.UTF8.GetBytes(StringUtil.ConvertToJson(differentRunnerSetting)));
|
||||||
|
_runnerServer.Setup(x => x.RefreshRunnerConfigAsync(It.IsAny<int>(), It.Is<string>(s => s == "runner"), It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(encodedConfig);
|
||||||
|
|
||||||
|
var _runnerConfigUpdater = new RunnerConfigUpdater();
|
||||||
|
_runnerConfigUpdater.Initialize(hc);
|
||||||
|
|
||||||
|
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
|
||||||
|
var configType = "runner";
|
||||||
|
var serviceType = "pipelines";
|
||||||
|
var configRefreshUrl = "http://example.com";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _runnerConfigUpdater.UpdateRunnerConfigAsync(validRunnerQualifiedId, configType, serviceType, configRefreshUrl);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>(s => s.Contains("Runner name in refreshed config")), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_configurationStore.Verify(x => x.SaveMigratedSettings(It.IsAny<RunnerSettings>()), Times.Never);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task UpdateRunnerConfigAsync_RefreshCredentialsWithDifferentScheme_ShouldReportTelemetry()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var setting = new RunnerSettings { AgentId = 1, AgentName = "agent1" };
|
||||||
|
_configurationStore.Setup(x => x.GetSettings()).Returns(setting);
|
||||||
|
var credData = new CredentialData
|
||||||
|
{
|
||||||
|
Scheme = "OAuth"
|
||||||
|
};
|
||||||
|
credData.Data.Add("ClientId", "12345");
|
||||||
|
_configurationStore.Setup(x => x.GetCredentials()).Returns(credData);
|
||||||
|
|
||||||
|
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
|
||||||
|
IOUtil.SaveObject(credData, hc.GetConfigFile(WellKnownConfigFile.Credentials));
|
||||||
|
|
||||||
|
var differentCredData = new CredentialData
|
||||||
|
{
|
||||||
|
Scheme = "PAT"
|
||||||
|
};
|
||||||
|
differentCredData.Data.Add("ClientId", "12345");
|
||||||
|
var encodedConfig = Convert.ToBase64String(Encoding.UTF8.GetBytes(StringUtil.ConvertToJson(differentCredData)));
|
||||||
|
_runnerServer.Setup(x => x.RefreshRunnerConfigAsync(It.IsAny<int>(), It.Is<string>(s => s == "credentials"), It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(encodedConfig);
|
||||||
|
|
||||||
|
var _runnerConfigUpdater = new RunnerConfigUpdater();
|
||||||
|
_runnerConfigUpdater.Initialize(hc);
|
||||||
|
|
||||||
|
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
|
||||||
|
var configType = "credentials";
|
||||||
|
var serviceType = "pipelines";
|
||||||
|
var configRefreshUrl = "http://example.com";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _runnerConfigUpdater.UpdateRunnerConfigAsync(validRunnerQualifiedId, configType, serviceType, configRefreshUrl);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>(s => s.Contains("Credential scheme in refreshed config")), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_configurationStore.Verify(x => x.SaveMigratedCredential(It.IsAny<CredentialData>()), Times.Never);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task UpdateRunnerConfigAsync_RefreshOAuthCredentialsWithDifferentClientId_ShouldReportTelemetry()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var setting = new RunnerSettings { AgentId = 1, AgentName = "agent1" };
|
||||||
|
_configurationStore.Setup(x => x.GetSettings()).Returns(setting);
|
||||||
|
var credData = new CredentialData
|
||||||
|
{
|
||||||
|
Scheme = "OAuth"
|
||||||
|
};
|
||||||
|
credData.Data.Add("clientId", "12345");
|
||||||
|
_configurationStore.Setup(x => x.GetCredentials()).Returns(credData);
|
||||||
|
|
||||||
|
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
|
||||||
|
IOUtil.SaveObject(credData, hc.GetConfigFile(WellKnownConfigFile.Credentials));
|
||||||
|
|
||||||
|
var differentCredData = new CredentialData
|
||||||
|
{
|
||||||
|
Scheme = "OAuth"
|
||||||
|
};
|
||||||
|
differentCredData.Data.Add("clientId", "67890");
|
||||||
|
var encodedConfig = Convert.ToBase64String(Encoding.UTF8.GetBytes(StringUtil.ConvertToJson(differentCredData)));
|
||||||
|
_runnerServer.Setup(x => x.RefreshRunnerConfigAsync(It.IsAny<int>(), It.Is<string>(s => s == "credentials"), It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(encodedConfig);
|
||||||
|
|
||||||
|
var _runnerConfigUpdater = new RunnerConfigUpdater();
|
||||||
|
_runnerConfigUpdater.Initialize(hc);
|
||||||
|
|
||||||
|
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
|
||||||
|
var configType = "credentials";
|
||||||
|
var serviceType = "pipelines";
|
||||||
|
var configRefreshUrl = "http://example.com";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _runnerConfigUpdater.UpdateRunnerConfigAsync(validRunnerQualifiedId, configType, serviceType, configRefreshUrl);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>(s => s.Contains("Credential clientId in refreshed config")), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_configurationStore.Verify(x => x.SaveMigratedCredential(It.IsAny<CredentialData>()), Times.Never);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task UpdateRunnerConfigAsync_RefreshOAuthCredentialsWithDifferentAuthUrl_ShouldReportTelemetry()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var setting = new RunnerSettings { AgentId = 1, AgentName = "agent1" };
|
||||||
|
_configurationStore.Setup(x => x.GetSettings()).Returns(setting);
|
||||||
|
var credData = new CredentialData
|
||||||
|
{
|
||||||
|
Scheme = "OAuth"
|
||||||
|
};
|
||||||
|
credData.Data.Add("clientId", "12345");
|
||||||
|
credData.Data.Add("authorizationUrl", "http://example.com/");
|
||||||
|
_configurationStore.Setup(x => x.GetCredentials()).Returns(credData);
|
||||||
|
|
||||||
|
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
|
||||||
|
IOUtil.SaveObject(credData, hc.GetConfigFile(WellKnownConfigFile.Credentials));
|
||||||
|
|
||||||
|
var differentCredData = new CredentialData
|
||||||
|
{
|
||||||
|
Scheme = "OAuth"
|
||||||
|
};
|
||||||
|
differentCredData.Data.Add("clientId", "12345");
|
||||||
|
differentCredData.Data.Add("authorizationUrl", "http://example2.com/");
|
||||||
|
var encodedConfig = Convert.ToBase64String(Encoding.UTF8.GetBytes(StringUtil.ConvertToJson(differentCredData)));
|
||||||
|
_runnerServer.Setup(x => x.RefreshRunnerConfigAsync(It.IsAny<int>(), It.Is<string>(s => s == "credentials"), It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(encodedConfig);
|
||||||
|
|
||||||
|
var _runnerConfigUpdater = new RunnerConfigUpdater();
|
||||||
|
_runnerConfigUpdater.Initialize(hc);
|
||||||
|
|
||||||
|
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
|
||||||
|
var configType = "credentials";
|
||||||
|
var serviceType = "pipelines";
|
||||||
|
var configRefreshUrl = "http://example.com";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _runnerConfigUpdater.UpdateRunnerConfigAsync(validRunnerQualifiedId, configType, serviceType, configRefreshUrl);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>(s => s.Contains("Credential authorizationUrl in refreshed config")), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_configurationStore.Verify(x => x.SaveMigratedCredential(It.IsAny<CredentialData>()), Times.Never);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task UpdateRunnerConfigAsync_UnsupportedServiceType_ShouldReportTelemetry()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var setting = new RunnerSettings { AgentId = 1, AgentName = "agent1" };
|
||||||
|
_configurationStore.Setup(x => x.GetSettings()).Returns(setting);
|
||||||
|
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
|
||||||
|
|
||||||
|
var _runnerConfigUpdater = new RunnerConfigUpdater();
|
||||||
|
_runnerConfigUpdater.Initialize(hc);
|
||||||
|
|
||||||
|
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
|
||||||
|
var configType = "runner";
|
||||||
|
var serviceType = "unsupported-service";
|
||||||
|
var configRefreshUrl = "http://example.com";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _runnerConfigUpdater.UpdateRunnerConfigAsync(validRunnerQualifiedId, configType, serviceType, configRefreshUrl);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>((s) => s.Contains("Invalid service type")), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_runnerServer.Verify(x => x.RefreshRunnerConfigAsync(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||||
|
_configurationStore.Verify(x => x.SaveMigratedSettings(It.IsAny<RunnerSettings>()), Times.Never);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task UpdateRunnerConfigAsync_RunnerAdminService_ShouldThrowNotSupported()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var setting = new RunnerSettings { AgentId = 1, AgentName = "agent1" };
|
||||||
|
_configurationStore.Setup(x => x.GetSettings()).Returns(setting);
|
||||||
|
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
|
||||||
|
|
||||||
|
var _runnerConfigUpdater = new RunnerConfigUpdater();
|
||||||
|
_runnerConfigUpdater.Initialize(hc);
|
||||||
|
|
||||||
|
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
|
||||||
|
var configType = "runner";
|
||||||
|
var serviceType = "runner-admin";
|
||||||
|
var configRefreshUrl = "http://example.com";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _runnerConfigUpdater.UpdateRunnerConfigAsync(validRunnerQualifiedId, configType, serviceType, configRefreshUrl);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>((s) => s.Contains("Runner admin service is not supported")), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_runnerServer.Verify(x => x.RefreshRunnerConfigAsync(It.IsAny<int>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||||
|
_configurationStore.Verify(x => x.SaveMigratedSettings(It.IsAny<RunnerSettings>()), Times.Never);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task UpdateRunnerConfigAsync_UpdateRunnerCredentials_EnableDisableAuthMigration()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var setting = new RunnerSettings { AgentId = 1, AgentName = "agent1" };
|
||||||
|
_configurationStore.Setup(x => x.GetSettings()).Returns(setting);
|
||||||
|
var credData = new CredentialData
|
||||||
|
{
|
||||||
|
Scheme = "OAuth"
|
||||||
|
};
|
||||||
|
credData.Data.Add("ClientId", "12345");
|
||||||
|
credData.Data.Add("AuthorizationUrl", "https://example.com");
|
||||||
|
credData.Data.Add("AuthorizationUrlV2", "https://example2.com");
|
||||||
|
_configurationStore.Setup(x => x.GetCredentials()).Returns(credData);
|
||||||
|
|
||||||
|
IOUtil.SaveObject(setting, hc.GetConfigFile(WellKnownConfigFile.Runner));
|
||||||
|
IOUtil.SaveObject(credData, hc.GetConfigFile(WellKnownConfigFile.Credentials));
|
||||||
|
|
||||||
|
var encodedConfig = Convert.ToBase64String(Encoding.UTF8.GetBytes(StringUtil.ConvertToJson(credData)));
|
||||||
|
_runnerServer.Setup(x => x.RefreshRunnerConfigAsync(It.IsAny<int>(), It.Is<string>(s => s == "credentials"), It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(encodedConfig);
|
||||||
|
|
||||||
|
var _runnerConfigUpdater = new RunnerConfigUpdater();
|
||||||
|
_runnerConfigUpdater.Initialize(hc);
|
||||||
|
Assert.False(hc.AllowAuthMigration);
|
||||||
|
|
||||||
|
var validRunnerQualifiedId = "valid/runner/qualifiedid/1";
|
||||||
|
var configType = "credentials";
|
||||||
|
var serviceType = "pipelines";
|
||||||
|
var configRefreshUrl = "http://example.com";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _runnerConfigUpdater.UpdateRunnerConfigAsync(validRunnerQualifiedId, configType, serviceType, configRefreshUrl);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_runnerServer.Verify(x => x.RefreshRunnerConfigAsync(1, "credentials", It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>(s => s.Contains("Runner credentials updated successfully")), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_configurationStore.Verify(x => x.SaveMigratedCredential(It.IsAny<CredentialData>()), Times.Once);
|
||||||
|
Assert.True(hc.AllowAuthMigration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
using GitHub.DistributedTask.WebApi;
|
using System;
|
||||||
using GitHub.Runner.Listener;
|
|
||||||
using GitHub.Runner.Listener.Configuration;
|
|
||||||
using Moq;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Runner.Listener;
|
||||||
|
using GitHub.Runner.Listener.Configuration;
|
||||||
|
using GitHub.Services.Common;
|
||||||
using GitHub.Services.WebApi;
|
using GitHub.Services.WebApi;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common.Tests.Listener
|
namespace GitHub.Runner.Common.Tests.Listener
|
||||||
@@ -24,6 +26,9 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
private Mock<IConfigurationStore> _configStore;
|
private Mock<IConfigurationStore> _configStore;
|
||||||
private Mock<ISelfUpdater> _updater;
|
private Mock<ISelfUpdater> _updater;
|
||||||
private Mock<IErrorThrottler> _acquireJobThrottler;
|
private Mock<IErrorThrottler> _acquireJobThrottler;
|
||||||
|
private Mock<ICredentialManager> _credentialManager;
|
||||||
|
private Mock<IActionsRunServer> _actionsRunServer;
|
||||||
|
private Mock<IRunServer> _runServer;
|
||||||
|
|
||||||
public RunnerL0()
|
public RunnerL0()
|
||||||
{
|
{
|
||||||
@@ -37,6 +42,9 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_configStore = new Mock<IConfigurationStore>();
|
_configStore = new Mock<IConfigurationStore>();
|
||||||
_updater = new Mock<ISelfUpdater>();
|
_updater = new Mock<ISelfUpdater>();
|
||||||
_acquireJobThrottler = new Mock<IErrorThrottler>();
|
_acquireJobThrottler = new Mock<IErrorThrottler>();
|
||||||
|
_credentialManager = new Mock<ICredentialManager>();
|
||||||
|
_actionsRunServer = new Mock<IActionsRunServer>();
|
||||||
|
_runServer = new Mock<IRunServer>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Pipelines.AgentJobRequestMessage CreateJobRequestMessage(string jobName)
|
private Pipelines.AgentJobRequestMessage CreateJobRequestMessage(string jobName)
|
||||||
@@ -57,7 +65,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
//process 2 new job messages, and one cancel message
|
//process 2 new job messages, and one cancel message
|
||||||
public async void TestRunAsync()
|
public async Task TestRunAsync()
|
||||||
{
|
{
|
||||||
using (var hc = new TestHostContext(this))
|
using (var hc = new TestHostContext(this))
|
||||||
{
|
{
|
||||||
@@ -118,7 +126,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
hc.EnqueueInstance<IJobDispatcher>(_jobDispatcher.Object);
|
hc.SetSingleton<IJobDispatcher>(_jobDispatcher.Object);
|
||||||
|
|
||||||
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
||||||
//Act
|
//Act
|
||||||
@@ -169,7 +177,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[MemberData(nameof(RunAsServiceTestData))]
|
[MemberData(nameof(RunAsServiceTestData))]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void TestExecuteCommandForRunAsService(string[] args, bool configureAsService, Times expectedTimes)
|
public async Task TestExecuteCommandForRunAsService(string[] args, bool configureAsService, Times expectedTimes)
|
||||||
{
|
{
|
||||||
using (var hc = new TestHostContext(this))
|
using (var hc = new TestHostContext(this))
|
||||||
{
|
{
|
||||||
@@ -177,6 +185,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
||||||
hc.SetSingleton<IMessageListener>(_messageListener.Object);
|
hc.SetSingleton<IMessageListener>(_messageListener.Object);
|
||||||
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
||||||
|
|
||||||
var command = new CommandSettings(hc, args);
|
var command = new CommandSettings(hc, args);
|
||||||
@@ -201,7 +210,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void TestMachineProvisionerCLI()
|
public async Task TestMachineProvisionerCLI()
|
||||||
{
|
{
|
||||||
using (var hc = new TestHostContext(this))
|
using (var hc = new TestHostContext(this))
|
||||||
{
|
{
|
||||||
@@ -209,6 +218,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
||||||
hc.SetSingleton<IMessageListener>(_messageListener.Object);
|
hc.SetSingleton<IMessageListener>(_messageListener.Object);
|
||||||
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
||||||
|
|
||||||
var command = new CommandSettings(hc, new[] { "run" });
|
var command = new CommandSettings(hc, new[] { "run" });
|
||||||
@@ -235,7 +245,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void TestRunOnce()
|
public async Task TestRunOnce()
|
||||||
{
|
{
|
||||||
using (var hc = new TestHostContext(this))
|
using (var hc = new TestHostContext(this))
|
||||||
{
|
{
|
||||||
@@ -299,7 +309,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
hc.EnqueueInstance<IJobDispatcher>(_jobDispatcher.Object);
|
hc.SetSingleton<IJobDispatcher>(_jobDispatcher.Object);
|
||||||
|
|
||||||
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
||||||
//Act
|
//Act
|
||||||
@@ -332,7 +342,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void TestRunOnceOnlyTakeOneJobMessage()
|
public async Task TestRunOnceOnlyTakeOneJobMessage()
|
||||||
{
|
{
|
||||||
using (var hc = new TestHostContext(this))
|
using (var hc = new TestHostContext(this))
|
||||||
{
|
{
|
||||||
@@ -403,7 +413,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
hc.EnqueueInstance<IJobDispatcher>(_jobDispatcher.Object);
|
hc.SetSingleton<IJobDispatcher>(_jobDispatcher.Object);
|
||||||
|
|
||||||
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
||||||
//Act
|
//Act
|
||||||
@@ -433,7 +443,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void TestRunOnceHandleUpdateMessage()
|
public async Task TestRunOnceHandleUpdateMessage()
|
||||||
{
|
{
|
||||||
using (var hc = new TestHostContext(this))
|
using (var hc = new TestHostContext(this))
|
||||||
{
|
{
|
||||||
@@ -493,7 +503,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
hc.EnqueueInstance<IJobDispatcher>(_jobDispatcher.Object);
|
hc.SetSingleton<IJobDispatcher>(_jobDispatcher.Object);
|
||||||
|
|
||||||
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
||||||
//Act
|
//Act
|
||||||
@@ -523,13 +533,14 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Runner")]
|
[Trait("Category", "Runner")]
|
||||||
public async void TestRemoveLocalRunnerConfig()
|
public async Task TestRemoveLocalRunnerConfig()
|
||||||
{
|
{
|
||||||
using (var hc = new TestHostContext(this))
|
using (var hc = new TestHostContext(this))
|
||||||
{
|
{
|
||||||
hc.SetSingleton<IConfigurationManager>(_configurationManager.Object);
|
hc.SetSingleton<IConfigurationManager>(_configurationManager.Object);
|
||||||
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
||||||
|
|
||||||
var command = new CommandSettings(hc, new[] { "remove", "--local" });
|
var command = new CommandSettings(hc, new[] { "remove", "--local" });
|
||||||
@@ -549,5 +560,521 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_configurationManager.Verify(x => x.DeleteLocalRunnerConfig(), Times.Once());
|
_configurationManager.Verify(x => x.DeleteLocalRunnerConfig(), Times.Once());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task TestReportAuthMigrationTelemetry()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
var runner = new Runner.Listener.Runner();
|
||||||
|
hc.SetSingleton<IConfigurationManager>(_configurationManager.Object);
|
||||||
|
hc.SetSingleton<IJobNotification>(_jobNotification.Object);
|
||||||
|
hc.SetSingleton<IMessageListener>(_messageListener.Object);
|
||||||
|
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
|
hc.SetSingleton<ICredentialManager>(_credentialManager.Object);
|
||||||
|
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
||||||
|
hc.SetSingleton<IJobDispatcher>(_jobDispatcher.Object);
|
||||||
|
|
||||||
|
runner.Initialize(hc);
|
||||||
|
var settings = new RunnerSettings
|
||||||
|
{
|
||||||
|
PoolId = 43242,
|
||||||
|
AgentId = 5678,
|
||||||
|
Ephemeral = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var message1 = new TaskAgentMessage()
|
||||||
|
{
|
||||||
|
MessageId = 4234,
|
||||||
|
MessageType = "unknown"
|
||||||
|
};
|
||||||
|
|
||||||
|
var messages = new Queue<TaskAgentMessage>();
|
||||||
|
messages.Enqueue(message1);
|
||||||
|
_updater.Setup(x => x.SelfUpdate(It.IsAny<AgentRefreshMessage>(), It.IsAny<IJobDispatcher>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(true));
|
||||||
|
_configurationManager.Setup(x => x.LoadSettings())
|
||||||
|
.Returns(settings);
|
||||||
|
_configurationManager.Setup(x => x.IsConfigured())
|
||||||
|
.Returns(true);
|
||||||
|
_messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult<CreateSessionResult>(CreateSessionResult.Success));
|
||||||
|
_messageListener.Setup(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(async (CancellationToken token) =>
|
||||||
|
{
|
||||||
|
hc.GetTrace().Info("Waiting for message");
|
||||||
|
Assert.False(hc.AllowAuthMigration);
|
||||||
|
await Task.Delay(100, token);
|
||||||
|
|
||||||
|
var traceFile = Path.GetTempFileName();
|
||||||
|
File.Copy(hc.TraceFileName, traceFile, true);
|
||||||
|
Assert.DoesNotContain("Checking for auth migration telemetry to report", File.ReadAllText(traceFile));
|
||||||
|
|
||||||
|
hc.EnableAuthMigration("L0Test");
|
||||||
|
hc.DeferAuthMigration(TimeSpan.FromSeconds(1), "L0Test");
|
||||||
|
hc.EnableAuthMigration("L0Test");
|
||||||
|
hc.DeferAuthMigration(TimeSpan.FromSeconds(1), "L0Test");
|
||||||
|
|
||||||
|
await Task.Delay(1000, token);
|
||||||
|
|
||||||
|
hc.ShutdownRunner(ShutdownReason.UserCancelled);
|
||||||
|
|
||||||
|
File.Copy(hc.TraceFileName, traceFile, true);
|
||||||
|
Assert.Contains("Checking for auth migration telemetry to report", File.ReadAllText(traceFile));
|
||||||
|
|
||||||
|
return messages.Dequeue();
|
||||||
|
});
|
||||||
|
_messageListener.Setup(x => x.DeleteSessionAsync())
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
_messageListener.Setup(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()))
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
_jobNotification.Setup(x => x.StartClient(It.IsAny<String>()))
|
||||||
|
.Callback(() =>
|
||||||
|
{
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
||||||
|
|
||||||
|
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(new TaskAgent()));
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var command = new CommandSettings(hc, new string[] { "run" });
|
||||||
|
var returnCode = await runner.ExecuteCommand(command);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
Assert.Equal(Constants.Runner.ReturnCode.Success, returnCode);
|
||||||
|
|
||||||
|
_messageListener.Verify(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()), Times.AtLeastOnce());
|
||||||
|
_messageListener.Verify(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()), Times.Once());
|
||||||
|
_messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once());
|
||||||
|
_messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.Once());
|
||||||
|
|
||||||
|
_runnerServer.Verify(x => x.UpdateAgentUpdateStateAsync(It.IsAny<int>(), It.IsAny<ulong>(), It.IsAny<string>(), It.Is<string>(s => s.Contains("L0Test")), It.IsAny<CancellationToken>()), Times.Exactly(4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task TestRunnerJobRequestMessageFromPipeline()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
var runner = new Runner.Listener.Runner();
|
||||||
|
hc.SetSingleton<IConfigurationManager>(_configurationManager.Object);
|
||||||
|
hc.SetSingleton<IJobNotification>(_jobNotification.Object);
|
||||||
|
hc.SetSingleton<IMessageListener>(_messageListener.Object);
|
||||||
|
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
|
hc.SetSingleton<ISelfUpdater>(_updater.Object);
|
||||||
|
hc.SetSingleton<ICredentialManager>(_credentialManager.Object);
|
||||||
|
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
||||||
|
hc.EnqueueInstance<IActionsRunServer>(_actionsRunServer.Object);
|
||||||
|
hc.SetSingleton<IJobDispatcher>(_jobDispatcher.Object);
|
||||||
|
|
||||||
|
runner.Initialize(hc);
|
||||||
|
var settings = new RunnerSettings
|
||||||
|
{
|
||||||
|
PoolId = 43242,
|
||||||
|
AgentId = 5678,
|
||||||
|
Ephemeral = true,
|
||||||
|
ServerUrl = "https://github.com",
|
||||||
|
};
|
||||||
|
|
||||||
|
var message1 = new TaskAgentMessage()
|
||||||
|
{
|
||||||
|
Body = JsonUtility.ToString(new RunnerJobRequestRef() { BillingOwnerId = "github", RunnerRequestId = "999" }),
|
||||||
|
MessageId = 4234,
|
||||||
|
MessageType = JobRequestMessageTypes.RunnerJobRequest
|
||||||
|
};
|
||||||
|
|
||||||
|
var messages = new Queue<TaskAgentMessage>();
|
||||||
|
messages.Enqueue(message1);
|
||||||
|
_updater.Setup(x => x.SelfUpdate(It.IsAny<AgentRefreshMessage>(), It.IsAny<IJobDispatcher>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(true));
|
||||||
|
_configurationManager.Setup(x => x.LoadSettings())
|
||||||
|
.Returns(settings);
|
||||||
|
_configurationManager.Setup(x => x.IsConfigured())
|
||||||
|
.Returns(true);
|
||||||
|
_messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult<CreateSessionResult>(CreateSessionResult.Success));
|
||||||
|
_messageListener.Setup(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(async (CancellationToken token) =>
|
||||||
|
{
|
||||||
|
if (0 == messages.Count)
|
||||||
|
{
|
||||||
|
await Task.Delay(2000, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages.Dequeue();
|
||||||
|
});
|
||||||
|
_messageListener.Setup(x => x.DeleteSessionAsync())
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
_messageListener.Setup(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()))
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
_jobNotification.Setup(x => x.StartClient(It.IsAny<String>()))
|
||||||
|
.Callback(() =>
|
||||||
|
{
|
||||||
|
|
||||||
|
});
|
||||||
|
_actionsRunServer.Setup(x => x.GetJobMessageAsync("999", It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(CreateJobRequestMessage("test")));
|
||||||
|
|
||||||
|
_credentialManager.Setup(x => x.LoadCredentials(false)).Returns(new VssCredentials());
|
||||||
|
|
||||||
|
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
||||||
|
|
||||||
|
var completedTask = new TaskCompletionSource<bool>();
|
||||||
|
completedTask.SetResult(true);
|
||||||
|
_jobDispatcher.Setup(x => x.RunOnceJobCompleted).Returns(completedTask);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var command = new CommandSettings(hc, new string[] { "run" });
|
||||||
|
Task<int> runnerTask = runner.ExecuteCommand(command);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
//wait for the runner to exit with right return code
|
||||||
|
await Task.WhenAny(runnerTask, Task.Delay(30000));
|
||||||
|
|
||||||
|
Assert.True(runnerTask.IsCompleted, $"{nameof(runner.ExecuteCommand)} timed out.");
|
||||||
|
Assert.True(!runnerTask.IsFaulted, runnerTask.Exception?.ToString());
|
||||||
|
if (runnerTask.IsCompleted)
|
||||||
|
{
|
||||||
|
Assert.Equal(Constants.Runner.ReturnCode.Success, await runnerTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
_jobDispatcher.Verify(x => x.Run(It.IsAny<Pipelines.AgentJobRequestMessage>(), true), Times.Once());
|
||||||
|
_messageListener.Verify(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()), Times.AtLeastOnce());
|
||||||
|
_messageListener.Verify(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()), Times.Once());
|
||||||
|
_messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once());
|
||||||
|
_messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.Once());
|
||||||
|
_credentialManager.Verify(x => x.LoadCredentials(false), Times.Once());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task TestRunnerJobRequestMessageFromRunService()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
var runner = new Runner.Listener.Runner();
|
||||||
|
hc.SetSingleton<IConfigurationManager>(_configurationManager.Object);
|
||||||
|
hc.SetSingleton<IJobNotification>(_jobNotification.Object);
|
||||||
|
hc.SetSingleton<IMessageListener>(_messageListener.Object);
|
||||||
|
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
|
hc.SetSingleton<ISelfUpdater>(_updater.Object);
|
||||||
|
hc.SetSingleton<ICredentialManager>(_credentialManager.Object);
|
||||||
|
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
||||||
|
hc.EnqueueInstance<IRunServer>(_runServer.Object);
|
||||||
|
hc.SetSingleton<IJobDispatcher>(_jobDispatcher.Object);
|
||||||
|
|
||||||
|
runner.Initialize(hc);
|
||||||
|
var settings = new RunnerSettings
|
||||||
|
{
|
||||||
|
PoolId = 43242,
|
||||||
|
AgentId = 5678,
|
||||||
|
Ephemeral = true,
|
||||||
|
ServerUrl = "https://github.com",
|
||||||
|
};
|
||||||
|
|
||||||
|
var message1 = new TaskAgentMessage()
|
||||||
|
{
|
||||||
|
Body = JsonUtility.ToString(new RunnerJobRequestRef() { BillingOwnerId = "github", RunnerRequestId = "999", RunServiceUrl = "https://run-service.com" }),
|
||||||
|
MessageId = 4234,
|
||||||
|
MessageType = JobRequestMessageTypes.RunnerJobRequest
|
||||||
|
};
|
||||||
|
|
||||||
|
var messages = new Queue<TaskAgentMessage>();
|
||||||
|
messages.Enqueue(message1);
|
||||||
|
_updater.Setup(x => x.SelfUpdate(It.IsAny<AgentRefreshMessage>(), It.IsAny<IJobDispatcher>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(true));
|
||||||
|
_configurationManager.Setup(x => x.LoadSettings())
|
||||||
|
.Returns(settings);
|
||||||
|
_configurationManager.Setup(x => x.IsConfigured())
|
||||||
|
.Returns(true);
|
||||||
|
_messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult<CreateSessionResult>(CreateSessionResult.Success));
|
||||||
|
_messageListener.Setup(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(async (CancellationToken token) =>
|
||||||
|
{
|
||||||
|
if (0 == messages.Count)
|
||||||
|
{
|
||||||
|
await Task.Delay(2000, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages.Dequeue();
|
||||||
|
});
|
||||||
|
_messageListener.Setup(x => x.DeleteSessionAsync())
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
_messageListener.Setup(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()))
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
_jobNotification.Setup(x => x.StartClient(It.IsAny<String>()))
|
||||||
|
.Callback(() =>
|
||||||
|
{
|
||||||
|
|
||||||
|
});
|
||||||
|
_runServer.Setup(x => x.GetJobMessageAsync("999", "github", It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(CreateJobRequestMessage("test")));
|
||||||
|
|
||||||
|
_credentialManager.Setup(x => x.LoadCredentials(true)).Returns(new VssCredentials());
|
||||||
|
|
||||||
|
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
||||||
|
|
||||||
|
var completedTask = new TaskCompletionSource<bool>();
|
||||||
|
completedTask.SetResult(true);
|
||||||
|
_jobDispatcher.Setup(x => x.RunOnceJobCompleted).Returns(completedTask);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var command = new CommandSettings(hc, new string[] { "run" });
|
||||||
|
Task<int> runnerTask = runner.ExecuteCommand(command);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
//wait for the runner to exit with right return code
|
||||||
|
await Task.WhenAny(runnerTask, Task.Delay(30000));
|
||||||
|
|
||||||
|
Assert.True(runnerTask.IsCompleted, $"{nameof(runner.ExecuteCommand)} timed out.");
|
||||||
|
Assert.True(!runnerTask.IsFaulted, runnerTask.Exception?.ToString());
|
||||||
|
if (runnerTask.IsCompleted)
|
||||||
|
{
|
||||||
|
Assert.Equal(Constants.Runner.ReturnCode.Success, await runnerTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
_jobDispatcher.Verify(x => x.Run(It.IsAny<Pipelines.AgentJobRequestMessage>(), true), Times.Once());
|
||||||
|
_messageListener.Verify(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()), Times.AtLeastOnce());
|
||||||
|
_messageListener.Verify(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()), Times.Once());
|
||||||
|
_messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once());
|
||||||
|
_messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.Once());
|
||||||
|
_credentialManager.Verify(x => x.LoadCredentials(true), Times.Once());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task TestRunnerJobRequestMessageFromRunService_AuthMigrationFallback()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
var runner = new Runner.Listener.Runner();
|
||||||
|
hc.SetSingleton<IConfigurationManager>(_configurationManager.Object);
|
||||||
|
hc.SetSingleton<IJobNotification>(_jobNotification.Object);
|
||||||
|
hc.SetSingleton<IMessageListener>(_messageListener.Object);
|
||||||
|
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
|
hc.SetSingleton<ISelfUpdater>(_updater.Object);
|
||||||
|
hc.SetSingleton<ICredentialManager>(_credentialManager.Object);
|
||||||
|
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
||||||
|
hc.SetSingleton<IJobDispatcher>(_jobDispatcher.Object);
|
||||||
|
hc.EnqueueInstance<IRunServer>(_runServer.Object);
|
||||||
|
hc.EnqueueInstance<IRunServer>(_runServer.Object);
|
||||||
|
|
||||||
|
runner.Initialize(hc);
|
||||||
|
var settings = new RunnerSettings
|
||||||
|
{
|
||||||
|
PoolId = 43242,
|
||||||
|
AgentId = 5678,
|
||||||
|
Ephemeral = true,
|
||||||
|
ServerUrl = "https://github.com",
|
||||||
|
};
|
||||||
|
|
||||||
|
var message1 = new TaskAgentMessage()
|
||||||
|
{
|
||||||
|
Body = JsonUtility.ToString(new RunnerJobRequestRef() { BillingOwnerId = "github", RunnerRequestId = "999", RunServiceUrl = "https://run-service.com" }),
|
||||||
|
MessageId = 4234,
|
||||||
|
MessageType = JobRequestMessageTypes.RunnerJobRequest
|
||||||
|
};
|
||||||
|
|
||||||
|
var messages = new Queue<TaskAgentMessage>();
|
||||||
|
messages.Enqueue(message1);
|
||||||
|
messages.Enqueue(message1);
|
||||||
|
_updater.Setup(x => x.SelfUpdate(It.IsAny<AgentRefreshMessage>(), It.IsAny<IJobDispatcher>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(true));
|
||||||
|
_configurationManager.Setup(x => x.LoadSettings())
|
||||||
|
.Returns(settings);
|
||||||
|
_configurationManager.Setup(x => x.IsConfigured())
|
||||||
|
.Returns(true);
|
||||||
|
_messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult<CreateSessionResult>(CreateSessionResult.Success));
|
||||||
|
_messageListener.Setup(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(async (CancellationToken token) =>
|
||||||
|
{
|
||||||
|
if (2 == messages.Count)
|
||||||
|
{
|
||||||
|
hc.EnableAuthMigration("L0Test");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 == messages.Count)
|
||||||
|
{
|
||||||
|
await Task.Delay(2000, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages.Dequeue();
|
||||||
|
});
|
||||||
|
_messageListener.Setup(x => x.DeleteSessionAsync())
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
_messageListener.Setup(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()))
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
_jobNotification.Setup(x => x.StartClient(It.IsAny<String>()))
|
||||||
|
.Callback(() =>
|
||||||
|
{
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var throwError = true;
|
||||||
|
_runServer.Setup(x => x.GetJobMessageAsync("999", "github", It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(() =>
|
||||||
|
{
|
||||||
|
if (throwError)
|
||||||
|
{
|
||||||
|
Assert.True(hc.AllowAuthMigration);
|
||||||
|
throwError = false;
|
||||||
|
throw new NotSupportedException("some error");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(CreateJobRequestMessage("test"));
|
||||||
|
});
|
||||||
|
|
||||||
|
_credentialManager.Setup(x => x.LoadCredentials(true)).Returns(new VssCredentials());
|
||||||
|
|
||||||
|
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
||||||
|
|
||||||
|
var completedTask = new TaskCompletionSource<bool>();
|
||||||
|
completedTask.SetResult(true);
|
||||||
|
_jobDispatcher.Setup(x => x.RunOnceJobCompleted).Returns(completedTask);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var command = new CommandSettings(hc, new string[] { "run" });
|
||||||
|
Task<int> runnerTask = runner.ExecuteCommand(command);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
//wait for the runner to exit with right return code
|
||||||
|
await Task.WhenAny(runnerTask, Task.Delay(30000));
|
||||||
|
|
||||||
|
Assert.True(runnerTask.IsCompleted, $"{nameof(runner.ExecuteCommand)} timed out.");
|
||||||
|
Assert.True(!runnerTask.IsFaulted, runnerTask.Exception?.ToString());
|
||||||
|
if (runnerTask.IsCompleted)
|
||||||
|
{
|
||||||
|
Assert.Equal(Constants.Runner.ReturnCode.Success, await runnerTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
_jobDispatcher.Verify(x => x.Run(It.IsAny<Pipelines.AgentJobRequestMessage>(), true), Times.Once());
|
||||||
|
_messageListener.Verify(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()), Times.Once());
|
||||||
|
_messageListener.Verify(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()), Times.AtLeast(2));
|
||||||
|
_messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.AtLeast(2));
|
||||||
|
_messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once());
|
||||||
|
_credentialManager.Verify(x => x.LoadCredentials(true), Times.Exactly(2));
|
||||||
|
|
||||||
|
Assert.False(hc.AllowAuthMigration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Runner")]
|
||||||
|
public async Task TestRunnerEnableAuthMigrationByDefault()
|
||||||
|
{
|
||||||
|
using (var hc = new TestHostContext(this))
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
var runner = new Runner.Listener.Runner();
|
||||||
|
hc.SetSingleton<IConfigurationManager>(_configurationManager.Object);
|
||||||
|
hc.SetSingleton<IJobNotification>(_jobNotification.Object);
|
||||||
|
hc.SetSingleton<IMessageListener>(_messageListener.Object);
|
||||||
|
hc.SetSingleton<IPromptManager>(_promptManager.Object);
|
||||||
|
hc.SetSingleton<IConfigurationStore>(_configStore.Object);
|
||||||
|
hc.SetSingleton<ICredentialManager>(_credentialManager.Object);
|
||||||
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
|
hc.EnqueueInstance<IErrorThrottler>(_acquireJobThrottler.Object);
|
||||||
|
|
||||||
|
runner.Initialize(hc);
|
||||||
|
var settings = new RunnerSettings
|
||||||
|
{
|
||||||
|
PoolId = 43242,
|
||||||
|
AgentId = 5678,
|
||||||
|
Ephemeral = true,
|
||||||
|
ServerUrl = "https://github.com",
|
||||||
|
};
|
||||||
|
|
||||||
|
var message1 = new TaskAgentMessage()
|
||||||
|
{
|
||||||
|
Body = JsonUtility.ToString(new RunnerJobRequestRef() { BillingOwnerId = "github", RunnerRequestId = "999", RunServiceUrl = "https://run-service.com" }),
|
||||||
|
MessageId = 4234,
|
||||||
|
MessageType = JobRequestMessageTypes.RunnerJobRequest
|
||||||
|
};
|
||||||
|
|
||||||
|
var messages = new Queue<TaskAgentMessage>();
|
||||||
|
messages.Enqueue(message1);
|
||||||
|
messages.Enqueue(message1);
|
||||||
|
_updater.Setup(x => x.SelfUpdate(It.IsAny<AgentRefreshMessage>(), It.IsAny<IJobDispatcher>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(true));
|
||||||
|
_configurationManager.Setup(x => x.LoadSettings())
|
||||||
|
.Returns(settings);
|
||||||
|
_configurationManager.Setup(x => x.IsConfigured())
|
||||||
|
.Returns(true);
|
||||||
|
_messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult<CreateSessionResult>(CreateSessionResult.Failure));
|
||||||
|
_jobNotification.Setup(x => x.StartClient(It.IsAny<String>()))
|
||||||
|
.Callback(() =>
|
||||||
|
{
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var throwError = true;
|
||||||
|
_runServer.Setup(x => x.GetJobMessageAsync("999", "github", It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(() =>
|
||||||
|
{
|
||||||
|
if (throwError)
|
||||||
|
{
|
||||||
|
Assert.True(hc.AllowAuthMigration);
|
||||||
|
throwError = false;
|
||||||
|
throw new NotSupportedException("some error");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(CreateJobRequestMessage("test"));
|
||||||
|
});
|
||||||
|
|
||||||
|
_credentialManager.Setup(x => x.LoadCredentials(true)).Returns(new VssCredentials());
|
||||||
|
|
||||||
|
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
||||||
|
|
||||||
|
var credData = new CredentialData()
|
||||||
|
{
|
||||||
|
Scheme = Constants.Configuration.OAuth,
|
||||||
|
};
|
||||||
|
credData.Data["ClientId"] = "testClientId";
|
||||||
|
credData.Data["AuthUrl"] = "https://github.com";
|
||||||
|
credData.Data["EnableAuthMigrationByDefault"] = "true";
|
||||||
|
_configStore.Setup(x => x.GetCredentials()).Returns(credData);
|
||||||
|
|
||||||
|
Assert.False(hc.AllowAuthMigration);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var command = new CommandSettings(hc, new string[] { "run" });
|
||||||
|
var returnCode = await runner.ExecuteCommand(command);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
Assert.Equal(Constants.Runner.ReturnCode.TerminatedError, returnCode);
|
||||||
|
|
||||||
|
_messageListener.Verify(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()), Times.Once());
|
||||||
|
|
||||||
|
Assert.True(hc.AllowAuthMigration);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,8 +107,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
hc.EnqueueInstance<IProcessInvoker>(p3);
|
hc.EnqueueInstance<IProcessInvoker>(p3);
|
||||||
updater.Initialize(hc);
|
updater.Initialize(hc);
|
||||||
|
|
||||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
.Callback((int p, ulong a, string s, string t) =>
|
.Callback((int p, ulong a, string s, string t, CancellationToken token) =>
|
||||||
{
|
{
|
||||||
hc.GetTrace().Info(t);
|
hc.GetTrace().Info(t);
|
||||||
})
|
})
|
||||||
@@ -168,8 +168,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_runnerServer.Setup(x => x.GetPackageAsync("agent", BuildConstants.RunnerPackage.PackageName, "2.200.0", true, It.IsAny<CancellationToken>()))
|
_runnerServer.Setup(x => x.GetPackageAsync("agent", BuildConstants.RunnerPackage.PackageName, "2.200.0", true, It.IsAny<CancellationToken>()))
|
||||||
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.200.0"), DownloadUrl = _packageUrl }));
|
.Returns(Task.FromResult(new PackageMetadata() { Platform = BuildConstants.RunnerPackage.PackageName, Version = new PackageVersion("2.200.0"), DownloadUrl = _packageUrl }));
|
||||||
|
|
||||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
.Callback((int p, ulong a, string s, string t) =>
|
.Callback((int p, ulong a, string s, string t, CancellationToken token) =>
|
||||||
{
|
{
|
||||||
hc.GetTrace().Info(t);
|
hc.GetTrace().Info(t);
|
||||||
})
|
})
|
||||||
@@ -220,8 +220,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
hc.EnqueueInstance<IProcessInvoker>(p3);
|
hc.EnqueueInstance<IProcessInvoker>(p3);
|
||||||
updater.Initialize(hc);
|
updater.Initialize(hc);
|
||||||
|
|
||||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
.Callback((int p, ulong a, string s, string t) =>
|
.Callback((int p, ulong a, string s, string t, CancellationToken token) =>
|
||||||
{
|
{
|
||||||
hc.GetTrace().Info(t);
|
hc.GetTrace().Info(t);
|
||||||
})
|
})
|
||||||
@@ -273,8 +273,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
hc.EnqueueInstance<IProcessInvoker>(p3);
|
hc.EnqueueInstance<IProcessInvoker>(p3);
|
||||||
updater.Initialize(hc);
|
updater.Initialize(hc);
|
||||||
|
|
||||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
.Callback((int p, ulong a, string s, string t) =>
|
.Callback((int p, ulong a, string s, string t, CancellationToken token) =>
|
||||||
{
|
{
|
||||||
hc.GetTrace().Info(t);
|
hc.GetTrace().Info(t);
|
||||||
})
|
})
|
||||||
|
|||||||
20
src/Test/L0/Sdk/RSWebApi/RunServiceHttpClientL0.cs
Normal file
20
src/Test/L0/Sdk/RSWebApi/RunServiceHttpClientL0.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using GitHub.Actions.RunService.WebApi;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace GitHub.Actions.RunService.WebApi.Tests;
|
||||||
|
|
||||||
|
public sealed class RunServiceHttpClientL0
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Truncate()
|
||||||
|
{
|
||||||
|
TestTruncate(string.Empty.PadLeft(199, 'a'), string.Empty.PadLeft(199, 'a'));
|
||||||
|
TestTruncate(string.Empty.PadLeft(200, 'a'), string.Empty.PadLeft(200, 'a'));
|
||||||
|
TestTruncate(string.Empty.PadLeft(201, 'a'), string.Empty.PadLeft(200, 'a') + "[truncated]");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TestTruncate(string errorBody, string expected)
|
||||||
|
{
|
||||||
|
Assert.Equal(expected, RunServiceHttpClient.Truncate(errorBody));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
using GitHub.Runner.Common.Util;
|
using System;
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.Loader;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Runtime.Loader;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using GitHub.DistributedTask.Logging;
|
using GitHub.DistributedTask.Logging;
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common.Tests
|
namespace GitHub.Runner.Common.Tests
|
||||||
@@ -31,6 +30,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
private StartupType _startupType;
|
private StartupType _startupType;
|
||||||
public event EventHandler Unloading;
|
public event EventHandler Unloading;
|
||||||
public event EventHandler<DelayEventArgs> Delaying;
|
public event EventHandler<DelayEventArgs> Delaying;
|
||||||
|
public event EventHandler<AuthMigrationEventArgs> AuthMigrationChanged;
|
||||||
public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token;
|
public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token;
|
||||||
public ShutdownReason RunnerShutdownReason { get; private set; }
|
public ShutdownReason RunnerShutdownReason { get; private set; }
|
||||||
public ISecretMasker SecretMasker => _secretMasker;
|
public ISecretMasker SecretMasker => _secretMasker;
|
||||||
@@ -92,6 +92,8 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
|
|
||||||
public RunnerWebProxy WebProxy => new();
|
public RunnerWebProxy WebProxy => new();
|
||||||
|
|
||||||
|
public bool AllowAuthMigration { get; set; }
|
||||||
|
|
||||||
public async Task Delay(TimeSpan delay, CancellationToken token)
|
public async Task Delay(TimeSpan delay, CancellationToken token)
|
||||||
{
|
{
|
||||||
// Event callback
|
// Event callback
|
||||||
@@ -101,8 +103,8 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
handler(this, new DelayEventArgs(delay, token));
|
handler(this, new DelayEventArgs(delay, token));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delay zero
|
// Delay 10ms
|
||||||
await Task.Delay(TimeSpan.Zero);
|
await Task.Delay(TimeSpan.FromMilliseconds(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
public T CreateService<T>() where T : class, IRunnerService
|
public T CreateService<T>() where T : class, IRunnerService
|
||||||
@@ -256,12 +258,24 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
".agent");
|
".agent");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case WellKnownConfigFile.MigratedRunner:
|
||||||
|
path = Path.Combine(
|
||||||
|
GetDirectory(WellKnownDirectory.Root),
|
||||||
|
".agent_migrated");
|
||||||
|
break;
|
||||||
|
|
||||||
case WellKnownConfigFile.Credentials:
|
case WellKnownConfigFile.Credentials:
|
||||||
path = Path.Combine(
|
path = Path.Combine(
|
||||||
GetDirectory(WellKnownDirectory.Root),
|
GetDirectory(WellKnownDirectory.Root),
|
||||||
".credentials");
|
".credentials");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case WellKnownConfigFile.MigratedCredentials:
|
||||||
|
path = Path.Combine(
|
||||||
|
GetDirectory(WellKnownDirectory.Root),
|
||||||
|
".credentials_migrated");
|
||||||
|
break;
|
||||||
|
|
||||||
case WellKnownConfigFile.RSACredentials:
|
case WellKnownConfigFile.RSACredentials:
|
||||||
path = Path.Combine(
|
path = Path.Combine(
|
||||||
GetDirectory(WellKnownDirectory.Root),
|
GetDirectory(WellKnownDirectory.Root),
|
||||||
@@ -325,7 +339,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
return _traceManager[name];
|
return _traceManager[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShutdownRunner(ShutdownReason reason)
|
public void ShutdownRunner(ShutdownReason reason, TimeSpan delay = default)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(reason, nameof(reason));
|
ArgUtil.NotNull(reason, nameof(reason));
|
||||||
RunnerShutdownReason = reason;
|
RunnerShutdownReason = reason;
|
||||||
@@ -375,6 +389,18 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void EnableAuthMigration(string trace)
|
||||||
|
{
|
||||||
|
AllowAuthMigration = true;
|
||||||
|
AuthMigrationChanged?.Invoke(this, new AuthMigrationEventArgs(trace));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeferAuthMigration(TimeSpan deferred, string trace)
|
||||||
|
{
|
||||||
|
AllowAuthMigration = false;
|
||||||
|
AuthMigrationChanged?.Invoke(this, new AuthMigrationEventArgs(trace));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DelayEventArgs : EventArgs
|
public class DelayEventArgs : EventArgs
|
||||||
|
|||||||
@@ -15,13 +15,12 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
<PackageReference Include="xunit" Version="2.7.1" />
|
<PackageReference Include="xunit" Version="2.7.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8" />
|
||||||
<PackageReference Include="System.Buffers" Version="4.5.1" />
|
|
||||||
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.7.0" />
|
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.7.0" />
|
||||||
<PackageReference Include="System.Threading.ThreadPool" Version="4.3.0" />
|
<PackageReference Include="System.Threading.ThreadPool" Version="4.3.0" />
|
||||||
<PackageReference Include="Moq" Version="4.20.70" />
|
<PackageReference Include="Moq" Version="4.20.72" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ LAYOUT_DIR="$SCRIPT_DIR/../_layout"
|
|||||||
DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
|
DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
|
||||||
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
||||||
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
||||||
DOTNETSDK_VERSION="8.0.404"
|
DOTNETSDK_VERSION="8.0.408"
|
||||||
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
||||||
RUNNER_VERSION=$(cat runnerversion)
|
RUNNER_VERSION=$(cat runnerversion)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"sdk": {
|
"sdk": {
|
||||||
"version": "8.0.404"
|
"version": "8.0.408"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.321.0
|
2.323.0
|
||||||
|
|||||||
Reference in New Issue
Block a user