mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:36:23 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8b82f44a9 | ||
|
|
6069f198b7 | ||
|
|
eb20b914ea | ||
|
|
6654f6b3de | ||
|
|
f5e4e7e47c | ||
|
|
68ca457917 | ||
|
|
77700abf81 | ||
|
|
a0ba8fd399 | ||
|
|
6b08f23b6c | ||
|
|
8131246933 | ||
|
|
7211a53c9e | ||
|
|
07310cabc0 | ||
|
|
0195d7ca77 | ||
|
|
259af3eda2 | ||
|
|
0ce29d09c6 | ||
|
|
a84e1c2b15 | ||
|
|
de51cd0ed6 | ||
|
|
3333de3a36 | ||
|
|
b065e5abbe | ||
|
|
bae52e28f9 | ||
|
|
c2c91438e8 | ||
|
|
3486c54ccb | ||
|
|
a61328a7e7 | ||
|
|
52dc98b10f | ||
|
|
a7b319530e | ||
|
|
54f082722f | ||
|
|
ed9d8fc9f7 |
@@ -4,7 +4,7 @@
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
||||
"ghcr.io/devcontainers/features/dotnet": {
|
||||
"version": "8.0.405"
|
||||
"version": "8.0.407"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"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
|
||||
about: If you have questions about GitHub Actions or need support writing workflows, please ask in the GitHub Community Support forum.
|
||||
- name: ✅ Feedback and suggestions for GitHub Actions
|
||||
url: https://github.com/github/feedback/discussions/categories/actions-and-packages-feedback
|
||||
url: https://github.com/github/feedback/discussions/categories/actions
|
||||
about: If you have feedback or suggestions about GitHub Actions, please open a discussion (or add to an existing one) in the GitHub Actions Feedback. GitHub Actions Product Managers and Engineers monitor the feedback forum.
|
||||
- name: ‼️ GitHub Security Bug Bounty
|
||||
url: https://bounty.github.com/
|
||||
|
||||
2
.github/workflows/close-bugs-bot.yml
vendored
2
.github/workflows/close-bugs-bot.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v8
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
close-issue-message: "This issue does not seem to be a problem with the runner application, it concerns the GitHub actions platform more generally. Could you please post your feedback on the [GitHub Community Support Forum](https://github.com/orgs/community/discussions/categories/actions) which is actively monitored. Using the forum ensures that we route your problem to the correct team. 😃"
|
||||
exempt-issue-labels: "keep"
|
||||
|
||||
2
.github/workflows/close-features-bot.yml
vendored
2
.github/workflows/close-features-bot.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v8
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
close-issue-message: "Thank you for your interest in the runner application and taking the time to provide your valuable feedback. We kindly ask you to redirect this feedback to the [GitHub Community Support Forum](https://github.com/orgs/community/discussions/categories/actions-and-packages) which our team actively monitors and would be a better place to start a discussion for new feature requests in GitHub Actions. For more information on this policy please [read our contribution guidelines](https://github.com/actions/runner#contribute). 😃"
|
||||
exempt-issue-labels: "keep"
|
||||
|
||||
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@v3
|
||||
|
||||
- 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
|
||||
23
.github/workflows/release.yml
vendored
23
.github/workflows/release.yml
vendored
@@ -16,11 +16,10 @@ jobs:
|
||||
# Make sure ./releaseVersion match ./src/runnerversion
|
||||
# Query GitHub release ensure version is not used
|
||||
- name: Check version
|
||||
uses: actions/github-script@0.3.0
|
||||
uses: actions/github-script@v7.0.1
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
const core = require('@actions/core')
|
||||
const fs = require('fs');
|
||||
const runnerVersion = fs.readFileSync('${{ github.workspace }}/src/runnerversion', 'utf8').replace(/\n$/g, '')
|
||||
const releaseVersion = fs.readFileSync('${{ github.workspace }}/releaseVersion', 'utf8').replace(/\n$/g, '')
|
||||
@@ -30,7 +29,7 @@ jobs:
|
||||
return
|
||||
}
|
||||
try {
|
||||
const release = await github.repos.getReleaseByTag({
|
||||
const release = await github.rest.repos.getReleaseByTag({
|
||||
owner: '${{ github.event.repository.owner.name }}',
|
||||
repo: '${{ github.event.repository.name }}',
|
||||
tag: 'v' + runnerVersion
|
||||
@@ -172,11 +171,10 @@ jobs:
|
||||
# Create ReleaseNote file
|
||||
- name: Create ReleaseNote
|
||||
id: releaseNote
|
||||
uses: actions/github-script@0.3.0
|
||||
uses: actions/github-script@v7.0.1
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
const core = require('@actions/core')
|
||||
const fs = require('fs');
|
||||
const runnerVersion = fs.readFileSync('${{ github.workspace }}/src/runnerversion', 'utf8').replace(/\n$/g, '')
|
||||
var releaseNote = fs.readFileSync('${{ github.workspace }}/releaseNote.md', 'utf8').replace(/<RUNNER_VERSION>/g, runnerVersion)
|
||||
@@ -291,6 +289,8 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository_owner }}/actions-runner
|
||||
@@ -300,7 +300,7 @@ jobs:
|
||||
|
||||
- name: Compute image version
|
||||
id: image
|
||||
uses: actions/github-script@v6
|
||||
uses: actions/github-script@v7.0.1
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
@@ -312,7 +312,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log into registry ${{ env.REGISTRY }}
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -320,7 +320,7 @@ jobs:
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: build-and-push
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./images
|
||||
platforms: |
|
||||
@@ -336,3 +336,10 @@ jobs:
|
||||
org.opencontainers.image.source=${{github.server_url}}/${{github.repository}}
|
||||
org.opencontainers.image.description=https://github.com/actions/runner/releases/tag/v${{ steps.image.outputs.version }}
|
||||
org.opencontainers.image.licenses=MIT
|
||||
|
||||
- name: Generate attestation
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
subject-digest: ${{ steps.build-and-push.outputs.digest }}
|
||||
push-to-registry: true
|
||||
|
||||
2
.github/workflows/stale-bot.yml
vendored
2
.github/workflows/stale-bot.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v8
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
stale-issue-message: "This issue is stale because it has been open 365 days with no activity. Remove stale label or comment or this will be closed in 15 days."
|
||||
close-issue-message: "This issue was closed because it has been stalled for 15 days with no activity."
|
||||
|
||||
@@ -23,7 +23,7 @@ This feature is mainly intended for self hosted runner administrators.
|
||||
- `ACTIONS_RUNNER_HOOK_JOB_STARTED`
|
||||
- `ACTIONS_RUNNER_HOOK_JOB_COMPLETED`
|
||||
|
||||
You can set these variables to the **absolute** path of a a `.sh` or `.ps1` file.
|
||||
You can set these variables to the **absolute** path of a `.sh` or `.ps1` file.
|
||||
|
||||
We will execute `pwsh` (fallback to `powershell`) or `bash` (fallback to `sh`) as appropriate.
|
||||
- `.sh` files will execute with the args `-e {pathtofile}`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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.
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# 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 TARGETARCH
|
||||
ARG RUNNER_VERSION
|
||||
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.6.1
|
||||
ARG DOCKER_VERSION=27.4.1
|
||||
ARG BUILDX_VERSION=0.19.3
|
||||
ARG DOCKER_VERSION=28.0.1
|
||||
ARG BUILDX_VERSION=0.21.2
|
||||
|
||||
RUN apt update -y && apt install curl unzip -y
|
||||
|
||||
|
||||
@@ -1,23 +1,36 @@
|
||||
## What's Changed
|
||||
* Fix name of generated of artifact builds from GitHub workflow for arm artifacts by @satmandu in https://github.com/actions/runner/pull/3568
|
||||
* Ignore error when fail to report worker crash. by @TingluoHuang in https://github.com/actions/runner/pull/3588
|
||||
* Fix null ref in 'OnEventWritten()' by @TingluoHuang in https://github.com/actions/runner/pull/3593
|
||||
* Send stepNumber for annotation to run-service by @TingluoHuang in https://github.com/actions/runner/pull/3614
|
||||
* Enable nuget audit. by @TingluoHuang in https://github.com/actions/runner/pull/3615
|
||||
* Update dotnet install script. by @TingluoHuang in https://github.com/actions/runner/pull/3659
|
||||
* Print immutable action package details in set up job logs by @heavymachinery in https://github.com/actions/runner/pull/3645
|
||||
* Update dotnet sdk to latest version @8.0.405 by @github-actions in https://github.com/actions/runner/pull/3666
|
||||
* Upgrade `buildx` from `0.18.0` to `0.19.3` (critical CVE) by @MPV in https://github.com/actions/runner/pull/3647
|
||||
* Upgrade `docker` from `27.3.1` to `27.4.1` by @MPV in https://github.com/actions/runner/pull/3648
|
||||
* Bump Microsoft.NET.Test.Sdk from 17.8.0 to 17.12.0 in /src by @dependabot in https://github.com/actions/runner/pull/3584
|
||||
* Bump docker/setup-buildx-action from 2 to 3 by @dependabot in https://github.com/actions/runner/pull/3564
|
||||
* Bump github/codeql-action from 2 to 3 by @dependabot in https://github.com/actions/runner/pull/3555
|
||||
* Bump Moq from 4.20.70 to 4.20.72 in /src by @dependabot in https://github.com/actions/runner/pull/3672
|
||||
* Bump docker/login-action from 2 to 3 by @dependabot in https://github.com/actions/runner/pull/3673
|
||||
* Bump actions/stale from 8 to 9 by @dependabot in https://github.com/actions/runner/pull/3554
|
||||
* Bump docker/build-push-action from 3 to 6 by @dependabot in https://github.com/actions/runner/pull/3674
|
||||
* update node version from 20.18.0 -> 20.18.2 by @aiqiaoy in https://github.com/actions/runner/pull/3682
|
||||
* Pass BillingOwnerId through Acquire/Complete calls by @luketomlinson in https://github.com/actions/runner/pull/3689
|
||||
* Do not retry CompleteJobAsync for known non-retryable errors by @ericsciple in https://github.com/actions/runner/pull/3696
|
||||
* Update dotnet sdk to latest version @8.0.406 by @github-actions in https://github.com/actions/runner/pull/3712
|
||||
* Update Dockerfile with new docker and buildx versions by @thboop in https://github.com/actions/runner/pull/3680
|
||||
* chore: remove redundant words by @finaltrip in https://github.com/actions/runner/pull/3705
|
||||
* fix: actions feedback link is incorrect by @Yaminyam in https://github.com/actions/runner/pull/3165
|
||||
* Bump actions/github-script from 0.3.0 to 7.0.1 by @dependabot in https://github.com/actions/runner/pull/3557
|
||||
* Docker container provenance by @paveliak in https://github.com/actions/runner/pull/3736
|
||||
* Add request-id to http eventsource trace. by @TingluoHuang in https://github.com/actions/runner/pull/3740
|
||||
* Update Bocker and Buildx version to mitigate images scanners alerts by @Blizter in https://github.com/actions/runner/pull/3750
|
||||
* Fix typo, add invariant culture to timestamp for workflow log reporting by @GhadimiR in https://github.com/actions/runner/pull/3749
|
||||
* Create vssconnection to actions service when URL provided. by @TingluoHuang in https://github.com/actions/runner/pull/3751
|
||||
* Housekeeping: Update npm packages and node version by @thboop in https://github.com/actions/runner/pull/3752
|
||||
* Improve the out-of-date warning message. by @tecimovic in https://github.com/actions/runner/pull/3595
|
||||
* Update dotnet sdk to latest version @8.0.407 by @github-actions in https://github.com/actions/runner/pull/3753
|
||||
* 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
|
||||
* @satmandu made their first contribution in https://github.com/actions/runner/pull/3568
|
||||
* @finaltrip made their first contribution in https://github.com/actions/runner/pull/3705
|
||||
* @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.321.0...v2.322.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.
|
||||
To confirm which version of the Actions Runner you should expect, please view the download instructions for your enterprise, organization, or repository.
|
||||
|
||||
@@ -1 +1 @@
|
||||
<Update to ./src/runnerversion when creating release>
|
||||
2.323.0
|
||||
|
||||
2
src/Misc/dotnet-install.sh
vendored
2
src/Misc/dotnet-install.sh
vendored
@@ -11,7 +11,7 @@ set -u
|
||||
# This is causing it to fail
|
||||
set -o pipefail
|
||||
|
||||
# Use in the the functions: eval $invocation
|
||||
# Use in the functions: eval $invocation
|
||||
invocation='say_verbose "Calling: ${yellow:-}${FUNCNAME[0]} ${green:-}$*${normal:-}"'
|
||||
|
||||
# standard output may be used as a return value in the functions
|
||||
|
||||
824
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
824
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,6 @@
|
||||
"pack": "ncc build -o ../../layoutbin/hashFiles",
|
||||
"all": "npm run format && npm run lint && npm run build && npm run pack",
|
||||
"prepare": "cd ../../../../ && husky install"
|
||||
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -43,9 +42,9 @@
|
||||
"eslint": "^8.47.0",
|
||||
"eslint-plugin-github": "^4.10.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"prettier": "^3.0.3",
|
||||
"typescript": "^5.2.2",
|
||||
"husky": "^8.0.3",
|
||||
"lint-staged": "^14.0.0"
|
||||
"lint-staged": "^15.5.0",
|
||||
"prettier": "^3.0.3",
|
||||
"typescript": "^5.2.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ NODE_URL=https://nodejs.org/dist
|
||||
NODE_ALPINE_URL=https://github.com/actions/alpine_nodejs/releases/download
|
||||
# When you update Node versions you must also create a new release of alpine_nodejs at that updated version.
|
||||
# Follow the instructions here: https://github.com/actions/alpine_nodejs?tab=readme-ov-file#getting-started
|
||||
NODE20_VERSION="20.18.0"
|
||||
NODE20_VERSION="20.19.0"
|
||||
|
||||
get_abs_path() {
|
||||
# exploits the fact that pwd will print abs path when no args
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace GitHub.Runner.Common
|
||||
|
||||
public bool ShouldRetryException(Exception ex)
|
||||
{
|
||||
if (ex is AccessDeniedException || ex is RunnerNotFoundException)
|
||||
if (ex is AccessDeniedException || ex is RunnerNotFoundException || ex is HostedRunnerDeprovisionedException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -119,8 +119,11 @@ namespace GitHub.Runner.Common
|
||||
CredentialData GetCredentials();
|
||||
CredentialData GetMigratedCredentials();
|
||||
RunnerSettings GetSettings();
|
||||
RunnerSettings GetMigratedSettings();
|
||||
void SaveCredential(CredentialData credential);
|
||||
void SaveMigratedCredential(CredentialData credential);
|
||||
void SaveSettings(RunnerSettings settings);
|
||||
void SaveMigratedSettings(RunnerSettings settings);
|
||||
void DeleteCredential();
|
||||
void DeleteMigratedCredential();
|
||||
void DeleteSettings();
|
||||
@@ -130,6 +133,7 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
private string _binPath;
|
||||
private string _configFilePath;
|
||||
private string _migratedConfigFilePath;
|
||||
private string _credFilePath;
|
||||
private string _migratedCredFilePath;
|
||||
private string _serviceConfigFilePath;
|
||||
@@ -137,6 +141,7 @@ namespace GitHub.Runner.Common
|
||||
private CredentialData _creds;
|
||||
private CredentialData _migratedCreds;
|
||||
private RunnerSettings _settings;
|
||||
private RunnerSettings _migratedSettings;
|
||||
|
||||
public override void Initialize(IHostContext hostContext)
|
||||
{
|
||||
@@ -154,6 +159,9 @@ namespace GitHub.Runner.Common
|
||||
_configFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Runner);
|
||||
Trace.Info("ConfigFilePath: {0}", _configFilePath);
|
||||
|
||||
_migratedConfigFilePath = hostContext.GetConfigFile(WellKnownConfigFile.MigratedRunner);
|
||||
Trace.Info("MigratedConfigFilePath: {0}", _migratedConfigFilePath);
|
||||
|
||||
_credFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Credentials);
|
||||
Trace.Info("CredFilePath: {0}", _credFilePath);
|
||||
|
||||
@@ -169,7 +177,7 @@ namespace GitHub.Runner.Common
|
||||
public bool HasCredentials()
|
||||
{
|
||||
Trace.Info("HasCredentials()");
|
||||
bool credsStored = (new FileInfo(_credFilePath)).Exists || (new FileInfo(_migratedCredFilePath)).Exists;
|
||||
bool credsStored = new FileInfo(_credFilePath).Exists || new FileInfo(_migratedCredFilePath).Exists;
|
||||
Trace.Info("stored {0}", credsStored);
|
||||
return credsStored;
|
||||
}
|
||||
@@ -177,7 +185,7 @@ namespace GitHub.Runner.Common
|
||||
public bool IsConfigured()
|
||||
{
|
||||
Trace.Info("IsConfigured()");
|
||||
bool configured = new FileInfo(_configFilePath).Exists;
|
||||
bool configured = new FileInfo(_configFilePath).Exists || new FileInfo(_migratedConfigFilePath).Exists;
|
||||
Trace.Info("IsConfigured: {0}", configured);
|
||||
return configured;
|
||||
}
|
||||
@@ -185,7 +193,7 @@ namespace GitHub.Runner.Common
|
||||
public bool IsServiceConfigured()
|
||||
{
|
||||
Trace.Info("IsServiceConfigured()");
|
||||
bool serviceConfigured = (new FileInfo(_serviceConfigFilePath)).Exists;
|
||||
bool serviceConfigured = new FileInfo(_serviceConfigFilePath).Exists;
|
||||
Trace.Info($"IsServiceConfigured: {serviceConfigured}");
|
||||
return serviceConfigured;
|
||||
}
|
||||
@@ -229,6 +237,25 @@ namespace GitHub.Runner.Common
|
||||
return _settings;
|
||||
}
|
||||
|
||||
public RunnerSettings GetMigratedSettings()
|
||||
{
|
||||
if (_migratedSettings == null)
|
||||
{
|
||||
RunnerSettings configuredSettings = null;
|
||||
if (File.Exists(_migratedConfigFilePath))
|
||||
{
|
||||
string json = File.ReadAllText(_migratedConfigFilePath, Encoding.UTF8);
|
||||
Trace.Info($"Read migrated setting file: {json.Length} chars");
|
||||
configuredSettings = StringUtil.ConvertFromJson<RunnerSettings>(json);
|
||||
}
|
||||
|
||||
ArgUtil.NotNull(configuredSettings, nameof(configuredSettings));
|
||||
_migratedSettings = configuredSettings;
|
||||
}
|
||||
|
||||
return _migratedSettings;
|
||||
}
|
||||
|
||||
public void SaveCredential(CredentialData credential)
|
||||
{
|
||||
Trace.Info("Saving {0} credential @ {1}", credential.Scheme, _credFilePath);
|
||||
@@ -244,6 +271,21 @@ namespace GitHub.Runner.Common
|
||||
File.SetAttributes(_credFilePath, File.GetAttributes(_credFilePath) | FileAttributes.Hidden);
|
||||
}
|
||||
|
||||
public void SaveMigratedCredential(CredentialData credential)
|
||||
{
|
||||
Trace.Info("Saving {0} migrated credential @ {1}", credential.Scheme, _migratedCredFilePath);
|
||||
if (File.Exists(_migratedCredFilePath))
|
||||
{
|
||||
// Delete existing credential file first, since the file is hidden and not able to overwrite.
|
||||
Trace.Info("Delete exist runner migrated credential file.");
|
||||
IOUtil.DeleteFile(_migratedCredFilePath);
|
||||
}
|
||||
|
||||
IOUtil.SaveObject(credential, _migratedCredFilePath);
|
||||
Trace.Info("Migrated Credentials Saved.");
|
||||
File.SetAttributes(_migratedCredFilePath, File.GetAttributes(_migratedCredFilePath) | FileAttributes.Hidden);
|
||||
}
|
||||
|
||||
public void SaveSettings(RunnerSettings settings)
|
||||
{
|
||||
Trace.Info("Saving runner settings.");
|
||||
@@ -259,6 +301,21 @@ namespace GitHub.Runner.Common
|
||||
File.SetAttributes(_configFilePath, File.GetAttributes(_configFilePath) | FileAttributes.Hidden);
|
||||
}
|
||||
|
||||
public void SaveMigratedSettings(RunnerSettings settings)
|
||||
{
|
||||
Trace.Info("Saving runner migrated settings");
|
||||
if (File.Exists(_migratedConfigFilePath))
|
||||
{
|
||||
// Delete existing settings file first, since the file is hidden and not able to overwrite.
|
||||
Trace.Info("Delete exist runner migrated settings file.");
|
||||
IOUtil.DeleteFile(_migratedConfigFilePath);
|
||||
}
|
||||
|
||||
IOUtil.SaveObject(settings, _migratedConfigFilePath);
|
||||
Trace.Info("Migrated Settings Saved.");
|
||||
File.SetAttributes(_migratedConfigFilePath, File.GetAttributes(_migratedConfigFilePath) | FileAttributes.Hidden);
|
||||
}
|
||||
|
||||
public void DeleteCredential()
|
||||
{
|
||||
IOUtil.Delete(_credFilePath, default(CancellationToken));
|
||||
@@ -273,6 +330,12 @@ namespace GitHub.Runner.Common
|
||||
public void DeleteSettings()
|
||||
{
|
||||
IOUtil.Delete(_configFilePath, default(CancellationToken));
|
||||
IOUtil.Delete(_migratedConfigFilePath, default(CancellationToken));
|
||||
}
|
||||
|
||||
public void DeleteMigratedSettings()
|
||||
{
|
||||
IOUtil.Delete(_migratedConfigFilePath, default(CancellationToken));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace GitHub.Runner.Common
|
||||
public enum WellKnownConfigFile
|
||||
{
|
||||
Runner,
|
||||
MigratedRunner,
|
||||
Credentials,
|
||||
MigratedCredentials,
|
||||
RSACredentials,
|
||||
@@ -160,6 +161,7 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
public static readonly string DiskSpaceWarning = "runner.diskspace.warning";
|
||||
public static readonly string LogTemplateErrorsAsDebugMessages = "DistributedTask.LogTemplateErrorsAsDebugMessages";
|
||||
public static readonly string SkipRetryCompleteJobUponKnownErrors = "actions_skip_retry_complete_job_upon_known_errors";
|
||||
public static readonly string UseContainerPathForTemplate = "DistributedTask.UseContainerPathForTemplate";
|
||||
public static readonly string AllowRunnerContainerHooks = "DistributedTask.AllowRunnerContainerHooks";
|
||||
}
|
||||
|
||||
@@ -343,6 +343,12 @@ namespace GitHub.Runner.Common
|
||||
".runner");
|
||||
break;
|
||||
|
||||
case WellKnownConfigFile.MigratedRunner:
|
||||
path = Path.Combine(
|
||||
GetDirectory(WellKnownDirectory.Root),
|
||||
".runner_migrated");
|
||||
break;
|
||||
|
||||
case WellKnownConfigFile.Credentials:
|
||||
path = Path.Combine(
|
||||
GetDirectory(WellKnownDirectory.Root),
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
Task ConnectAsync(Uri serverUrl, VssCredentials credentials);
|
||||
|
||||
Task<AgentJobRequestMessage> GetJobMessageAsync(string id, CancellationToken token);
|
||||
Task<AgentJobRequestMessage> GetJobMessageAsync(string id, string billingOwnerId, CancellationToken token);
|
||||
|
||||
Task CompleteJobAsync(
|
||||
Guid planId,
|
||||
@@ -29,6 +29,19 @@ namespace GitHub.Runner.Common
|
||||
IList<Annotation> jobAnnotations,
|
||||
string environmentUrl,
|
||||
IList<Telemetry> telemetry,
|
||||
string billingOwnerId,
|
||||
CancellationToken token);
|
||||
|
||||
Task CompleteJob2Async(
|
||||
Guid planId,
|
||||
Guid jobId,
|
||||
TaskResult result,
|
||||
Dictionary<String, VariableValue> outputs,
|
||||
IList<StepResult> stepResults,
|
||||
IList<Annotation> jobAnnotations,
|
||||
string environmentUrl,
|
||||
IList<Telemetry> telemetry,
|
||||
string billingOwnerId,
|
||||
CancellationToken token);
|
||||
|
||||
Task<RenewJobResponse> RenewJobAsync(Guid planId, Guid jobId, CancellationToken token);
|
||||
@@ -58,17 +71,18 @@ namespace GitHub.Runner.Common
|
||||
}
|
||||
}
|
||||
|
||||
public Task<AgentJobRequestMessage> GetJobMessageAsync(string id, CancellationToken cancellationToken)
|
||||
public Task<AgentJobRequestMessage> GetJobMessageAsync(string id, string billingOwnerId, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckConnection();
|
||||
return RetryRequest<AgentJobRequestMessage>(
|
||||
async () => await _runServiceHttpClient.GetJobMessageAsync(requestUri, id, VarUtil.OS, cancellationToken), cancellationToken,
|
||||
async () => await _runServiceHttpClient.GetJobMessageAsync(requestUri, id, VarUtil.OS, billingOwnerId, cancellationToken), cancellationToken,
|
||||
shouldRetry: ex =>
|
||||
ex is not TaskOrchestrationJobNotFoundException && // HTTP status 404
|
||||
ex is not TaskOrchestrationJobAlreadyAcquiredException && // HTTP status 409
|
||||
ex is not TaskOrchestrationJobUnprocessableException); // HTTP status 422
|
||||
}
|
||||
|
||||
// Legacy will be deleted when SkipRetryCompleteJobUponKnownErrors is cleaned up
|
||||
public Task CompleteJobAsync(
|
||||
Guid planId,
|
||||
Guid jobId,
|
||||
@@ -78,11 +92,32 @@ namespace GitHub.Runner.Common
|
||||
IList<Annotation> jobAnnotations,
|
||||
string environmentUrl,
|
||||
IList<Telemetry> telemetry,
|
||||
string billingOwnerId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
CheckConnection();
|
||||
return RetryRequest(
|
||||
async () => await _runServiceHttpClient.CompleteJobAsync(requestUri, planId, jobId, result, outputs, stepResults, jobAnnotations, environmentUrl, telemetry, cancellationToken), cancellationToken);
|
||||
async () => await _runServiceHttpClient.CompleteJobAsync(requestUri, planId, jobId, result, outputs, stepResults, jobAnnotations, environmentUrl, telemetry, billingOwnerId, cancellationToken), cancellationToken);
|
||||
}
|
||||
|
||||
public Task CompleteJob2Async(
|
||||
Guid planId,
|
||||
Guid jobId,
|
||||
TaskResult result,
|
||||
Dictionary<String, VariableValue> outputs,
|
||||
IList<StepResult> stepResults,
|
||||
IList<Annotation> jobAnnotations,
|
||||
string environmentUrl,
|
||||
IList<Telemetry> telemetry,
|
||||
string billingOwnerId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
CheckConnection();
|
||||
return RetryRequest(
|
||||
async () => await _runServiceHttpClient.CompleteJobAsync(requestUri, planId, jobId, result, outputs, stepResults, jobAnnotations, environmentUrl, 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)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Services.WebApi;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.WebApi;
|
||||
|
||||
namespace GitHub.Runner.Common
|
||||
{
|
||||
@@ -50,7 +50,10 @@ namespace GitHub.Runner.Common
|
||||
Task<PackageMetadata> GetPackageAsync(string packageType, string platform, string version, bool includeToken, CancellationToken cancellationToken);
|
||||
|
||||
// agent update
|
||||
Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, ulong agentId, string currentState, string trace);
|
||||
Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, ulong agentId, string currentState, string trace, CancellationToken cancellationToken = default);
|
||||
|
||||
// runner config refresh
|
||||
Task<string> RefreshRunnerConfigAsync(int agentId, string configType, string encodedRunnerConfig, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public sealed class RunnerServer : RunnerService, IRunnerServer
|
||||
@@ -315,10 +318,17 @@ namespace GitHub.Runner.Common
|
||||
return _genericTaskAgentClient.GetPackageAsync(packageType, platform, version, includeToken, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, ulong agentId, string currentState, string trace)
|
||||
public Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, ulong agentId, string currentState, string trace, CancellationToken cancellationToken = default)
|
||||
{
|
||||
CheckConnection(RunnerConnectionType.Generic);
|
||||
return _genericTaskAgentClient.UpdateAgentUpdateStateAsync(agentPoolId, agentId, currentState, trace);
|
||||
return _genericTaskAgentClient.UpdateAgentUpdateStateAsync(agentPoolId, agentId, currentState, trace, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
// runner config refresh
|
||||
public Task<string> RefreshRunnerConfigAsync(int agentId, string configType, string encodedRunnerConfig, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckConnection(RunnerConnectionType.Generic);
|
||||
return _genericTaskAgentClient.RefreshRunnerConfigAsync(agentId, configType, encodedRunnerConfig, cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,8 @@ namespace GitHub.Runner.Common
|
||||
|
||||
protected async Task RetryRequest(Func<Task> func,
|
||||
CancellationToken cancellationToken,
|
||||
int maxRetryAttemptsCount = 5
|
||||
int maxRetryAttemptsCount = 5,
|
||||
Func<Exception, bool> shouldRetry = null
|
||||
)
|
||||
{
|
||||
async Task<Unit> wrappedFunc()
|
||||
@@ -78,7 +79,7 @@ namespace GitHub.Runner.Common
|
||||
await func();
|
||||
return Unit.Value;
|
||||
}
|
||||
await RetryRequest<Unit>(wrappedFunc, cancellationToken, maxRetryAttemptsCount);
|
||||
await RetryRequest<Unit>(wrappedFunc, cancellationToken, maxRetryAttemptsCount, shouldRetry);
|
||||
}
|
||||
|
||||
protected async Task<T> RetryRequest<T>(Func<Task<T>> func,
|
||||
|
||||
@@ -9,9 +9,9 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Listener.Configuration;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.OAuth;
|
||||
using GitHub.Services.WebApi;
|
||||
@@ -27,6 +27,7 @@ namespace GitHub.Runner.Listener
|
||||
private CancellationTokenSource _getMessagesTokenSource;
|
||||
private VssCredentials _creds;
|
||||
private TaskAgentSession _session;
|
||||
private IRunnerServer _runnerServer;
|
||||
private IBrokerServer _brokerServer;
|
||||
private readonly Dictionary<string, int> _sessionCreationExceptionTracker = new();
|
||||
private bool _accessTokenRevoked = false;
|
||||
@@ -40,6 +41,7 @@ namespace GitHub.Runner.Listener
|
||||
base.Initialize(hostContext);
|
||||
|
||||
_term = HostContext.GetService<ITerminal>();
|
||||
_runnerServer = HostContext.GetService<IRunnerServer>();
|
||||
_brokerServer = HostContext.GetService<IBrokerServer>();
|
||||
}
|
||||
|
||||
@@ -50,7 +52,8 @@ namespace GitHub.Runner.Listener
|
||||
// Settings
|
||||
var configManager = HostContext.GetService<IConfigurationManager>();
|
||||
_settings = configManager.LoadSettings();
|
||||
var serverUrl = _settings.ServerUrlV2;
|
||||
var serverUrlV2 = _settings.ServerUrlV2;
|
||||
var serverUrl = _settings.ServerUrl;
|
||||
Trace.Info(_settings);
|
||||
|
||||
if (string.IsNullOrEmpty(_settings.ServerUrlV2))
|
||||
@@ -84,9 +87,17 @@ namespace GitHub.Runner.Listener
|
||||
try
|
||||
{
|
||||
Trace.Info("Connecting to the Broker Server...");
|
||||
await _brokerServer.ConnectAsync(new Uri(serverUrl), _creds);
|
||||
await _brokerServer.ConnectAsync(new Uri(serverUrlV2), _creds);
|
||||
Trace.Info("VssConnection created");
|
||||
|
||||
if (!string.IsNullOrEmpty(serverUrl) &&
|
||||
!string.Equals(serverUrl, serverUrlV2, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Trace.Info("Connecting to the Runner server...");
|
||||
await _runnerServer.ConnectAsync(new Uri(serverUrl), _creds);
|
||||
Trace.Info("VssConnection created");
|
||||
}
|
||||
|
||||
_term.WriteLine();
|
||||
_term.WriteSuccessMessage("Connected to GitHub");
|
||||
_term.WriteLine();
|
||||
@@ -131,7 +142,7 @@ namespace GitHub.Runner.Listener
|
||||
// Check whether we get 401 because the runner registration already removed by the service.
|
||||
// If the runner registration get deleted, we can't exchange oauth token.
|
||||
Trace.Error("Test oauth app registration.");
|
||||
var oauthTokenProvider = new VssOAuthTokenProvider(vssOAuthCred, new Uri(serverUrl));
|
||||
var oauthTokenProvider = new VssOAuthTokenProvider(vssOAuthCred, new Uri(serverUrlV2));
|
||||
var authError = await oauthTokenProvider.ValidateCredentialAsync(token);
|
||||
if (string.Equals(authError, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -238,6 +249,11 @@ namespace GitHub.Runner.Listener
|
||||
Trace.Info("Runner OAuth token has been revoked. Unable to pull message.");
|
||||
throw;
|
||||
}
|
||||
catch (HostedRunnerDeprovisionedException)
|
||||
{
|
||||
Trace.Info("Hosted runner has been deprovisioned.");
|
||||
throw;
|
||||
}
|
||||
catch (AccessDeniedException e) when (e.ErrorCode == 1)
|
||||
{
|
||||
throw;
|
||||
|
||||
@@ -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");
|
||||
|
||||
// We will Combine() what's stored with root. Defaults to string a relative path
|
||||
|
||||
@@ -1206,7 +1206,7 @@ namespace GitHub.Runner.Listener
|
||||
jobAnnotations.Add(annotation.Value);
|
||||
}
|
||||
|
||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, TaskResult.Failed, outputs: null, stepResults: null, jobAnnotations: jobAnnotations, environmentUrl: null, 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)
|
||||
{
|
||||
|
||||
@@ -304,6 +304,11 @@ namespace GitHub.Runner.Listener
|
||||
_accessTokenRevoked = true;
|
||||
throw;
|
||||
}
|
||||
catch (HostedRunnerDeprovisionedException)
|
||||
{
|
||||
Trace.Info("Hosted runner has been deprovisioned.");
|
||||
throw;
|
||||
}
|
||||
catch (AccessDeniedException e) when (e.ErrorCode == 1)
|
||||
{
|
||||
throw;
|
||||
@@ -528,7 +533,8 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
else if (ex is TaskAgentPoolNotFoundException ||
|
||||
ex is AccessDeniedException ||
|
||||
ex is VssUnauthorizedException)
|
||||
ex is VssUnauthorizedException ||
|
||||
(ex is VssOAuthTokenRequestException oauthEx && oauthEx.Error != "server_error"))
|
||||
{
|
||||
Trace.Info($"Non-retriable exception: {ex.Message}");
|
||||
return false;
|
||||
|
||||
@@ -360,7 +360,7 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
}
|
||||
|
||||
private IMessageListener GetMesageListener(RunnerSettings settings)
|
||||
private IMessageListener GetMessageListener(RunnerSettings settings)
|
||||
{
|
||||
if (settings.UseV2Flow)
|
||||
{
|
||||
@@ -379,7 +379,7 @@ namespace GitHub.Runner.Listener
|
||||
try
|
||||
{
|
||||
Trace.Info(nameof(RunAsync));
|
||||
_listener = GetMesageListener(settings);
|
||||
_listener = GetMessageListener(settings);
|
||||
CreateSessionResult createSessionResult = await _listener.CreateSessionAsync(HostContext.RunnerShutdownToken);
|
||||
if (createSessionResult == CreateSessionResult.SessionConflict)
|
||||
{
|
||||
@@ -584,7 +584,7 @@ namespace GitHub.Runner.Listener
|
||||
await runServer.ConnectAsync(new Uri(messageRef.RunServiceUrl), creds);
|
||||
try
|
||||
{
|
||||
jobRequestMessage = await runServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
||||
jobRequestMessage = await runServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageRef.BillingOwnerId, messageQueueLoopTokenSource.Token);
|
||||
_acquireJobThrottler.Reset();
|
||||
}
|
||||
catch (Exception ex) when (
|
||||
@@ -635,6 +635,17 @@ namespace GitHub.Runner.Listener
|
||||
Trace.Info("Received ForceTokenRefreshMessage");
|
||||
await _listener.RefreshListenerTokenAsync(messageQueueLoopTokenSource.Token);
|
||||
}
|
||||
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
|
||||
{
|
||||
Trace.Error($"Received message {message.MessageId} with unsupported message type {message.MessageType}.");
|
||||
@@ -697,6 +708,10 @@ namespace GitHub.Runner.Listener
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
267
src/Runner.Listener/RunnerConfigUpdater.cs
Normal file
267
src/Runner.Listener/RunnerConfigUpdater.cs
Normal file
@@ -0,0 +1,267 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// save the refreshed runner credentials as a separate file
|
||||
_store.SaveMigratedCredential(refreshedCredConfig);
|
||||
await ReportTelemetryAsync("Runner credentials updated successfully.");
|
||||
}
|
||||
|
||||
private async Task<bool> VerifyRunnerQualifiedId(string runnerQualifiedId)
|
||||
{
|
||||
Trace.Entering();
|
||||
Trace.Info($"Verifying runner qualified id: {runnerQualifiedId}");
|
||||
var idParts = runnerQualifiedId.Split("/", StringSplitOptions.RemoveEmptyEntries);
|
||||
if (idParts.Length != 4 || idParts[3] != _settings.AgentId.ToString())
|
||||
{
|
||||
Trace.Error($"Runner qualified id '{runnerQualifiedId}' does not match the current runner '{_settings.AgentId}'.");
|
||||
await ReportTelemetryAsync($"Runner qualified id '{runnerQualifiedId}' does not match the current runner '{_settings.AgentId}'.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<string> RefreshRunnerConfigAsync(string encodedConfig, string serviceType, string configType, string configRefreshUrl, CancellationToken token)
|
||||
{
|
||||
string refreshedEncodedConfig;
|
||||
switch (serviceType.ToLowerInvariant())
|
||||
{
|
||||
case "pipelines":
|
||||
try
|
||||
{
|
||||
refreshedEncodedConfig = await _runnerServer.RefreshRunnerConfigAsync((int)_settings.AgentId, configType, encodedConfig, token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error($"Failed to refresh runner {configType} config with service.");
|
||||
Trace.Error(ex);
|
||||
await ReportTelemetryAsync($"Failed to refresh {configType} config: {ex}");
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case "runner-admin":
|
||||
throw new NotSupportedException("Runner admin service is not supported.");
|
||||
default:
|
||||
Trace.Error($"Invalid service type '{serviceType}'.");
|
||||
await ReportTelemetryAsync($"Invalid service type '{serviceType}'.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return refreshedEncodedConfig;
|
||||
}
|
||||
|
||||
private async Task ReportTelemetryAsync(string telemetry)
|
||||
{
|
||||
Trace.Entering();
|
||||
try
|
||||
{
|
||||
using (var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
|
||||
{
|
||||
await _runnerServer.UpdateAgentUpdateStateAsync(_settings.PoolId, _settings.AgentId, "RefreshConfig", telemetry, tokenSource.Token);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error("Failed to report telemetry.");
|
||||
Trace.Error(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,14 @@ namespace GitHub.Runner.Listener
|
||||
{
|
||||
[DataMember(Name = "id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[DataMember(Name = "runner_request_id")]
|
||||
public string RunnerRequestId { get; set; }
|
||||
|
||||
[DataMember(Name = "run_service_url")]
|
||||
public string RunServiceUrl { get; set; }
|
||||
|
||||
[DataMember(Name = "billing_owner_id")]
|
||||
public string BillingOwnerId { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,9 +318,30 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
try
|
||||
{
|
||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, result, jobContext.JobOutputs, jobContext.Global.StepsResult, jobContext.Global.JobAnnotations, environmentUrl, telemetry, default);
|
||||
if (jobContext.Global.Variables.GetBoolean(Constants.Runner.Features.SkipRetryCompleteJobUponKnownErrors) ?? false)
|
||||
{
|
||||
await runServer.CompleteJob2Async(message.Plan.PlanId, message.JobId, result, jobContext.JobOutputs, jobContext.Global.StepsResult, jobContext.Global.JobAnnotations, environmentUrl, telemetry, billingOwnerId: message.BillingOwnerId, default);
|
||||
}
|
||||
else
|
||||
{
|
||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, result, jobContext.JobOutputs, jobContext.Global.StepsResult, jobContext.Global.JobAnnotations, environmentUrl, telemetry, billingOwnerId: message.BillingOwnerId, default);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (VssUnauthorizedException ex) when (jobContext.Global.Variables.GetBoolean(Constants.Runner.Features.SkipRetryCompleteJobUponKnownErrors) ?? false)
|
||||
{
|
||||
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) when (jobContext.Global.Variables.GetBoolean(Constants.Runner.Features.SkipRetryCompleteJobUponKnownErrors) ?? false)
|
||||
{
|
||||
Trace.Error($"Catch exception while attempting to complete job {message.JobId}, job request {message.RequestId}.");
|
||||
Trace.Error(ex);
|
||||
exceptions.Add(ex);
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error($"Catch exception while attempting to complete job {message.JobId}, job request {message.RequestId}.");
|
||||
@@ -511,7 +532,7 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Tracing;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Sockets;
|
||||
@@ -335,7 +336,25 @@ namespace GitHub.Services.Common.Diagnostics
|
||||
if (IsEnabled())
|
||||
{
|
||||
SetActivityId(activity);
|
||||
HttpRequestStop(response.RequestMessage.GetHttpMethod(), response.RequestMessage.RequestUri.AbsoluteUri, (Int32)response.StatusCode);
|
||||
var requestId = "NoExpectedHeader";
|
||||
if (response.Headers != null)
|
||||
{
|
||||
if (response.Headers.TryGetValues("x-github-request-id", out var headerValues) && headerValues != null)
|
||||
{
|
||||
requestId = headerValues.FirstOrDefault();
|
||||
}
|
||||
else if (response.Headers.TryGetValues("x-vss-e2eid", out headerValues) && headerValues != null)
|
||||
{
|
||||
requestId = headerValues.FirstOrDefault();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(requestId))
|
||||
{
|
||||
requestId = "NoExpectedHeader";
|
||||
}
|
||||
}
|
||||
|
||||
HttpRequestStop(response.RequestMessage.GetHttpMethod(), response.RequestMessage.RequestUri.AbsoluteUri, (Int32)response.StatusCode, requestId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -747,15 +766,16 @@ namespace GitHub.Services.Common.Diagnostics
|
||||
}
|
||||
}
|
||||
|
||||
[Event(24, Level = EventLevel.Verbose, Task = Tasks.HttpRequest, Opcode = EventOpcode.Stop, Message = "Finished {0} request to {1} with status code {2}")]
|
||||
[Event(24, Level = EventLevel.Verbose, Task = Tasks.HttpRequest, Opcode = EventOpcode.Stop, Message = "Finished {0} request to {1} with status code {2} ({3})")]
|
||||
private void HttpRequestStop(
|
||||
VssHttpMethod method,
|
||||
String url,
|
||||
Int32 statusCode)
|
||||
Int32 statusCode,
|
||||
String requestId)
|
||||
{
|
||||
if (IsEnabled())
|
||||
{
|
||||
WriteEvent(24, (Int32)method, url, statusCode);
|
||||
WriteEvent(24, (Int32)method, url, statusCode, requestId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,8 +23,8 @@ using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Formatting;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Services.Common;
|
||||
@@ -827,5 +827,36 @@ namespace GitHub.DistributedTask.WebApi
|
||||
userState: userState,
|
||||
cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [Preview API]
|
||||
/// </summary>
|
||||
/// <param name="agentId"></param>
|
||||
/// <param name="configType"></param>
|
||||
/// <param name="encodedRunnerConfig"></param>
|
||||
/// <param name="userState"></param>
|
||||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public virtual Task<string> RefreshRunnerConfigAsync(
|
||||
int agentId,
|
||||
string configType,
|
||||
string encodedRunnerConfig,
|
||||
object userState = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
HttpMethod httpMethod = new HttpMethod("POST");
|
||||
Guid locationId = new Guid("13b5d709-74aa-470b-a8e9-bf9f3ded3f18");
|
||||
object routeValues = new { agentId = agentId, configType = configType };
|
||||
HttpContent content = new ObjectContent<string>(encodedRunnerConfig, new VssJsonMediaTypeFormatter(true));
|
||||
|
||||
return SendAsync<string>(
|
||||
httpMethod,
|
||||
locationId,
|
||||
routeValues: routeValues,
|
||||
version: new ApiResourceVersion(6.0, 1),
|
||||
userState: userState,
|
||||
cancellationToken: cancellationToken,
|
||||
content: content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,6 +246,13 @@ namespace GitHub.DistributedTask.Pipelines
|
||||
set;
|
||||
}
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public String BillingOwnerId
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of variables associated with the current context.
|
||||
/// </summary>
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
||||
{
|
||||
if (!NameValidation.IsValid(value, allowHyphens: true) && value.Length < PipelineConstants.MaxNodeNameLength)
|
||||
{
|
||||
error = $"The identifier '{value}' is invalid. IDs may only contain alphanumeric characters, '_', and '-'. IDs must start with a letter or '_' and and must be less than {PipelineConstants.MaxNodeNameLength} characters.";
|
||||
error = $"The identifier '{value}' is invalid. IDs may only contain alphanumeric characters, '_', and '-'. IDs must start with a letter or '_' and must be less than {PipelineConstants.MaxNodeNameLength} characters.";
|
||||
return false;
|
||||
}
|
||||
else if (!m_distinctNames.Add(value))
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace GitHub.DistributedTask.WebApi
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The url to connect to to poll for messages
|
||||
/// The url to connect to poll for messages
|
||||
/// </summary>
|
||||
[JsonProperty("server_url")]
|
||||
public string ServerUrl
|
||||
|
||||
58
src/Sdk/DTWebApi/WebApi/RunnerRefreshConfigMessage.cs
Normal file
58
src/Sdk/DTWebApi/WebApi/RunnerRefreshConfigMessage.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.Services.WebApi;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace GitHub.DistributedTask.WebApi
|
||||
{
|
||||
[DataContract]
|
||||
public sealed class RunnerRefreshConfigMessage
|
||||
{
|
||||
public static readonly String MessageType = "RunnerRefreshConfig";
|
||||
|
||||
[JsonConstructor]
|
||||
internal RunnerRefreshConfigMessage()
|
||||
{
|
||||
}
|
||||
|
||||
public RunnerRefreshConfigMessage(
|
||||
string runnerQualifiedId,
|
||||
string configType,
|
||||
string serviceType,
|
||||
string configRefreshUrl)
|
||||
{
|
||||
this.RunnerQualifiedId = runnerQualifiedId;
|
||||
this.ConfigType = configType;
|
||||
this.ServiceType = serviceType;
|
||||
this.ConfigRefreshUrl = configRefreshUrl;
|
||||
}
|
||||
|
||||
[DataMember(Name = "runnerQualifiedId")]
|
||||
public String RunnerQualifiedId
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[DataMember(Name = "configType")]
|
||||
public String ConfigType
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[DataMember(Name = "serviceType")]
|
||||
public String ServiceType
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[DataMember(Name = "configRefreshURL")]
|
||||
public String ConfigRefreshUrl
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,5 +10,8 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
|
||||
[DataMember(Name = "runnerOS", EmitDefaultValue = false)]
|
||||
public string RunnerOS { get; set; }
|
||||
|
||||
[DataMember(Name = "billingOwnerId", EmitDefaultValue = false)]
|
||||
public string BillingOwnerId { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ namespace Sdk.RSWebApi.Contracts
|
||||
[DataMember(Name = "message", EmitDefaultValue = false)]
|
||||
public string Message;
|
||||
|
||||
[DataMember(Name = "title", EmitDefaultValue = false)]
|
||||
public string Title;
|
||||
|
||||
[DataMember(Name = "rawDetails", EmitDefaultValue = false)]
|
||||
public string RawDetails;
|
||||
|
||||
|
||||
@@ -7,5 +7,6 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
{
|
||||
public const string RunnerNotFound = "RunnerNotFound";
|
||||
public const string RunnerVersionTooOld = "RunnerVersionTooOld";
|
||||
public const string HostedRunnerDeprovisioned = "HostedRunnerDeprovisioned";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,5 +32,8 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
|
||||
[DataMember(Name = "environmentUrl", EmitDefaultValue = false)]
|
||||
public string EnvironmentUrl { get; set; }
|
||||
|
||||
[DataMember(Name = "billingOwnerId", EmitDefaultValue = false)]
|
||||
public string BillingOwnerId { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Sdk.RSWebApi.Contracts
|
||||
var endColumnNumber = GetAnnotationNumber(issue, RunIssueKeys.EndColumn) ?? columnNumber;
|
||||
var logLineNumber = GetAnnotationNumber(issue, RunIssueKeys.LogLineNumber) ?? 0;
|
||||
var stepNumber = GetAnnotationNumber(issue, RunIssueKeys.StepNumber) ?? 0;
|
||||
var title = GetAnnotationField(issue, RunIssueKeys.Title);
|
||||
|
||||
if (path == null && lineNumber == 0 && logLineNumber != 0)
|
||||
{
|
||||
@@ -34,6 +35,7 @@ namespace Sdk.RSWebApi.Contracts
|
||||
{
|
||||
Level = annotationLevel,
|
||||
Message = issueMessage,
|
||||
Title = title,
|
||||
Path = path,
|
||||
StartLine = lineNumber,
|
||||
EndLine = endLineNumber,
|
||||
|
||||
@@ -10,5 +10,6 @@
|
||||
public const string EndColumn = "endColumn";
|
||||
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,
|
||||
string messageId,
|
||||
string runnerOS,
|
||||
string billingOwnerId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
HttpMethod httpMethod = new HttpMethod("POST");
|
||||
var payload = new AcquireJobRequest
|
||||
{
|
||||
JobMessageId = messageId,
|
||||
RunnerOS = runnerOS
|
||||
RunnerOS = runnerOS,
|
||||
BillingOwnerId = billingOwnerId,
|
||||
};
|
||||
|
||||
requestUri = new Uri(requestUri, "acquirejob");
|
||||
@@ -128,6 +130,7 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
IList<Annotation> jobAnnotations,
|
||||
string environmentUrl,
|
||||
IList<Telemetry> telemetry,
|
||||
string billingOwnerId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
HttpMethod httpMethod = new HttpMethod("POST");
|
||||
@@ -141,6 +144,7 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
Annotations = jobAnnotations,
|
||||
EnvironmentUrl = environmentUrl,
|
||||
Telemetry = telemetry,
|
||||
BillingOwnerId = billingOwnerId,
|
||||
};
|
||||
|
||||
requestUri = new Uri(requestUri, "completejob");
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<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.Pkcs" Version="8.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
||||
|
||||
@@ -122,6 +122,8 @@ namespace GitHub.Actions.RunService.WebApi
|
||||
{
|
||||
ErrorCode = 1
|
||||
};
|
||||
case BrokerErrorKind.HostedRunnerDeprovisioned:
|
||||
throw new HostedRunnerDeprovisionedException(brokerError.Message);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
namespace GitHub.Services.WebApi
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class HostedRunnerDeprovisionedException : Exception
|
||||
{
|
||||
public HostedRunnerDeprovisionedException()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public HostedRunnerDeprovisionedException(String message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public HostedRunnerDeprovisionedException(String message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -677,7 +677,7 @@ namespace GitHub.Services.WebApi.Location
|
||||
Int32 lastChangeId = m_locationDataCacheManager.GetLastChangeId();
|
||||
|
||||
// If we have -1 then that means we have no disk cache yet or it means that we recently hit an exception trying to reload
|
||||
// the the cache from disk (see Exception catch block in EnsureDiskCacheLoaded).
|
||||
// the cache from disk (see Exception catch block in EnsureDiskCacheLoaded).
|
||||
// Either way, we cannot make a call to the server with -1 and pass None.
|
||||
// If we do, the resulting payload (which would have ClientCacheFresh=false but include no ServiceDefinitions)
|
||||
// would leave the in-memory cache in an inconsistent state
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
@@ -132,7 +133,7 @@ namespace GitHub.Services.Results.Client
|
||||
|
||||
private async Task StepSummaryUploadCompleteAsync(string planId, string jobId, Guid stepId, long size, CancellationToken cancellationToken)
|
||||
{
|
||||
var timestamp = DateTime.UtcNow.ToString(Constants.TimestampFormat);
|
||||
var timestamp = DateTime.UtcNow.ToString(Constants.TimestampFormat, CultureInfo.InvariantCulture);
|
||||
var request = new StepSummaryMetadataCreate()
|
||||
{
|
||||
WorkflowJobRunBackendId = jobId,
|
||||
@@ -148,7 +149,7 @@ namespace GitHub.Services.Results.Client
|
||||
|
||||
private async Task StepLogUploadCompleteAsync(string planId, string jobId, Guid stepId, long lineCount, CancellationToken cancellationToken)
|
||||
{
|
||||
var timestamp = DateTime.UtcNow.ToString(Constants.TimestampFormat);
|
||||
var timestamp = DateTime.UtcNow.ToString(Constants.TimestampFormat, CultureInfo.InvariantCulture);
|
||||
var request = new StepLogsMetadataCreate()
|
||||
{
|
||||
WorkflowJobRunBackendId = jobId,
|
||||
@@ -164,7 +165,7 @@ namespace GitHub.Services.Results.Client
|
||||
|
||||
private async Task JobLogUploadCompleteAsync(string planId, string jobId, long lineCount, CancellationToken cancellationToken)
|
||||
{
|
||||
var timestamp = DateTime.UtcNow.ToString(Constants.TimestampFormat);
|
||||
var timestamp = DateTime.UtcNow.ToString(Constants.TimestampFormat, CultureInfo.InvariantCulture);
|
||||
var request = new JobLogsMetadataCreate()
|
||||
{
|
||||
WorkflowJobRunBackendId = jobId,
|
||||
@@ -565,7 +566,7 @@ namespace GitHub.Services.Results.Client
|
||||
|
||||
public async Task UpdateWorkflowStepsAsync(Guid planId, IEnumerable<TimelineRecord> records, CancellationToken cancellationToken)
|
||||
{
|
||||
var timestamp = DateTime.UtcNow.ToString(Constants.TimestampFormat);
|
||||
var timestamp = DateTime.UtcNow.ToString(Constants.TimestampFormat, CultureInfo.InvariantCulture);
|
||||
var stepRecords = records.Where(r => String.Equals(r.RecordType, "Task", StringComparison.Ordinal));
|
||||
var stepUpdateRequests = stepRecords.GroupBy(r => r.ParentId).Select(sg => new StepsUpdateRequest()
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
private readonly RunnerSettings _settings;
|
||||
private readonly Mock<IConfigurationManager> _config;
|
||||
private readonly Mock<IBrokerServer> _brokerServer;
|
||||
private readonly Mock<IRunnerServer> _runnerServer;
|
||||
private readonly Mock<ICredentialManager> _credMgr;
|
||||
private Mock<IConfigurationStore> _store;
|
||||
|
||||
@@ -28,6 +29,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
_credMgr = new Mock<ICredentialManager>();
|
||||
_store = new Mock<IConfigurationStore>();
|
||||
_brokerServer = new Mock<IBrokerServer>();
|
||||
_runnerServer = new Mock<IRunnerServer>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -75,6 +77,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
tc.SetSingleton<ICredentialManager>(_credMgr.Object);
|
||||
tc.SetSingleton<IConfigurationStore>(_store.Object);
|
||||
tc.SetSingleton<IBrokerServer>(_brokerServer.Object);
|
||||
tc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||
return tc;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,20 +36,23 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
_configurationStore = new Mock<IConfigurationStore>();
|
||||
}
|
||||
|
||||
private Pipelines.AgentJobRequestMessage CreateJobRequestMessage()
|
||||
private Pipelines.AgentJobRequestMessage CreateJobRequestMessage(string billingOwnerId = null)
|
||||
{
|
||||
TaskOrchestrationPlanReference plan = new();
|
||||
TimelineReference timeline = null;
|
||||
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);
|
||||
result.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
|
||||
result.BillingOwnerId = billingOwnerId;
|
||||
return result;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Theory]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Runner")]
|
||||
public async void DispatchesJobRequest()
|
||||
[InlineData(null)]
|
||||
[InlineData("billingOwnerId")]
|
||||
public async void DispatchesJobRequest(string billingOwnerId)
|
||||
{
|
||||
//Arrange
|
||||
using (var hc = new TestHostContext(this))
|
||||
@@ -65,7 +68,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
jobDispatcher.Initialize(hc);
|
||||
|
||||
var ts = new CancellationTokenSource();
|
||||
Pipelines.AgentJobRequestMessage message = CreateJobRequestMessage();
|
||||
Pipelines.AgentJobRequestMessage message = CreateJobRequestMessage(billingOwnerId);
|
||||
string strMessage = JsonUtility.ToString(message);
|
||||
|
||||
_processInvoker.Setup(x => x.ExecuteAsync(It.IsAny<String>(), It.IsAny<String>(), "spawnclient 1 2", null, It.IsAny<CancellationToken>()))
|
||||
|
||||
579
src/Test/L0/Listener/RunnerConfigUpdaterTests.cs
Normal file
579
src/Test/L0/Listener/RunnerConfigUpdaterTests.cs
Normal file
@@ -0,0 +1,579 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Runner.Listener;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Sdk;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using System.Threading;
|
||||
using GitHub.Runner.Common.Tests;
|
||||
using System.Text;
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
[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_RefreshRunnerCredetialsFailure_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_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,8 +107,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
hc.EnqueueInstance<IProcessInvoker>(p3);
|
||||
updater.Initialize(hc);
|
||||
|
||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((int p, ulong a, string s, string t) =>
|
||||
_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, CancellationToken token) =>
|
||||
{
|
||||
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>()))
|
||||
.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>()))
|
||||
.Callback((int p, ulong a, string s, string t) =>
|
||||
_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, CancellationToken token) =>
|
||||
{
|
||||
hc.GetTrace().Info(t);
|
||||
})
|
||||
@@ -220,8 +220,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
hc.EnqueueInstance<IProcessInvoker>(p3);
|
||||
updater.Initialize(hc);
|
||||
|
||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((int p, ulong a, string s, string t) =>
|
||||
_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, CancellationToken token) =>
|
||||
{
|
||||
hc.GetTrace().Info(t);
|
||||
})
|
||||
@@ -273,8 +273,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
hc.EnqueueInstance<IProcessInvoker>(p3);
|
||||
updater.Initialize(hc);
|
||||
|
||||
_runnerServer.Setup(x => x.UpdateAgentUpdateStateAsync(1, 1, It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((int p, ulong a, string s, string t) =>
|
||||
_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, CancellationToken token) =>
|
||||
{
|
||||
hc.GetTrace().Info(t);
|
||||
})
|
||||
|
||||
@@ -256,12 +256,24 @@ namespace GitHub.Runner.Common.Tests
|
||||
".agent");
|
||||
break;
|
||||
|
||||
case WellKnownConfigFile.MigratedRunner:
|
||||
path = Path.Combine(
|
||||
GetDirectory(WellKnownDirectory.Root),
|
||||
".agent_migrated");
|
||||
break;
|
||||
|
||||
case WellKnownConfigFile.Credentials:
|
||||
path = Path.Combine(
|
||||
GetDirectory(WellKnownDirectory.Root),
|
||||
".credentials");
|
||||
break;
|
||||
|
||||
case WellKnownConfigFile.MigratedCredentials:
|
||||
path = Path.Combine(
|
||||
GetDirectory(WellKnownDirectory.Root),
|
||||
".credentials_migrated");
|
||||
break;
|
||||
|
||||
case WellKnownConfigFile.RSACredentials:
|
||||
path = Path.Combine(
|
||||
GetDirectory(WellKnownDirectory.Root),
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="xunit" Version="2.7.1" />
|
||||
<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.Threading.ThreadPool" Version="4.3.0" />
|
||||
<PackageReference Include="Moq" Version="4.20.72" />
|
||||
|
||||
@@ -17,7 +17,7 @@ LAYOUT_DIR="$SCRIPT_DIR/../_layout"
|
||||
DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
|
||||
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
||||
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
||||
DOTNETSDK_VERSION="8.0.405"
|
||||
DOTNETSDK_VERSION="8.0.407"
|
||||
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
||||
RUNNER_VERSION=$(cat runnerversion)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "8.0.405"
|
||||
"version": "8.0.407"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.322.0
|
||||
2.323.0
|
||||
|
||||
Reference in New Issue
Block a user