mirror of
https://github.com/actions/runner.git
synced 2025-12-10 20:36:49 +00:00
Compare commits
92 Commits
v2.297.0
...
johnsudol/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3cbaea9cb8 | ||
|
|
1acad3fee2 | ||
|
|
995ef97863 | ||
|
|
0fabfa67ca | ||
|
|
c40f389f13 | ||
|
|
ec0e6c72eb | ||
|
|
6cdd031970 | ||
|
|
7b0a262a43 | ||
|
|
689a94d915 | ||
|
|
8e35cbcc36 | ||
|
|
8ef48200b4 | ||
|
|
d61b27b839 | ||
|
|
542e8a3c98 | ||
|
|
e8975514fd | ||
|
|
0befa62f64 | ||
|
|
aaf02ab34c | ||
|
|
02c9d1c704 | ||
|
|
982784d704 | ||
|
|
8c096baf49 | ||
|
|
8d6972e38b | ||
|
|
1ab35b0938 | ||
|
|
f86e968d38 | ||
|
|
e979331be4 | ||
|
|
97195bad58 | ||
|
|
6d1d2460ac | ||
|
|
67356a3305 | ||
|
|
9a228e52e9 | ||
|
|
3cd76671dd | ||
|
|
e6e5f36dd0 | ||
|
|
24a27efd4f | ||
|
|
ca7be16dd3 | ||
|
|
f1c57ac0ef | ||
|
|
8581a041a5 | ||
|
|
6412390a22 | ||
|
|
7306014861 | ||
|
|
d6f8633efc | ||
|
|
130f6788d5 | ||
|
|
9b390e0531 | ||
|
|
a7101008a2 | ||
|
|
4a6630531b | ||
|
|
caec043085 | ||
|
|
a1244d2269 | ||
|
|
332b97f838 | ||
|
|
72830cfc12 | ||
|
|
29a28a870f | ||
|
|
0dd7a113f1 | ||
|
|
83b8baa45e | ||
|
|
d5e566ad17 | ||
|
|
64381cca6a | ||
|
|
f1b1532f32 | ||
|
|
04761e5353 | ||
|
|
f9e2fa939c | ||
|
|
92acb625fb | ||
|
|
6b9e8a6be4 | ||
|
|
f41f5d259d | ||
|
|
369a4eccad | ||
|
|
088981a372 | ||
|
|
852a80fcbd | ||
|
|
63640e91fa | ||
|
|
9122fe7e10 | ||
|
|
6b8452170a | ||
|
|
cc49e65356 | ||
|
|
1632e4a343 | ||
|
|
b465102e7f | ||
|
|
98c857b927 | ||
|
|
dda53af485 | ||
|
|
c0bc4c02f8 | ||
|
|
c6630ce285 | ||
|
|
40ed7f8a40 | ||
|
|
7f5067a8b5 | ||
|
|
4adaf9c1e6 | ||
|
|
d301c06a7e | ||
|
|
3e196355de | ||
|
|
dad7ad0384 | ||
|
|
b18bda773f | ||
|
|
3fc993da59 | ||
|
|
5421fe3f71 | ||
|
|
b87b4aac5c | ||
|
|
daba735b52 | ||
|
|
46ce960fd2 | ||
|
|
f4b7f91c21 | ||
|
|
ff65183e43 | ||
|
|
0f13055428 | ||
|
|
252f4de577 | ||
|
|
b6a46f2114 | ||
|
|
2145432f81 | ||
|
|
86d0ee8389 | ||
|
|
1379ed2c72 | ||
|
|
4935be5526 | ||
|
|
920fba93dc | ||
|
|
949269104d | ||
|
|
dca4f67143 |
27
.devcontainer/devcontainer.json
Normal file
27
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,27 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||
{
|
||||
"name": "Actions Runner Devcontainer",
|
||||
"image": "mcr.microsoft.com/devcontainers/base:focal",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
||||
"ghcr.io/devcontainers/features/dotnet": {
|
||||
"version": "6.0.405"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "16"
|
||||
}
|
||||
},
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"ms-azuretools.vscode-docker",
|
||||
"ms-dotnettools.csharp",
|
||||
"eamodio.gitlens"
|
||||
]
|
||||
}
|
||||
},
|
||||
// dotnet restore to install dependencies so OmniSharp works out of the box
|
||||
// src/Test restores all other projects it references, src/Runner.PluginHost is not one of them
|
||||
"postCreateCommand": "dotnet restore src/Test && dotnet restore src/Runner.PluginHost",
|
||||
"remoteUser": "vscode"
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
# https://editorconfig.org/
|
||||
|
||||
[*]
|
||||
charset = utf-8 # Set default charset to utf-8
|
||||
insert_final_newline = true # ensure all files end with a single newline
|
||||
trim_trailing_whitespace = true # attempt to remove trailing whitespace on save
|
||||
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 🛑 Request a feature in the runner application
|
||||
url: https://github.com/orgs/community/discussions/categories/actions-and-packages
|
||||
about: If you have feature requests for GitHub Actions, please use the Actions and Packages section on the Github Product Feedback page.
|
||||
- name: ✅ Support for GitHub Actions
|
||||
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.
|
||||
|
||||
32
.github/ISSUE_TEMPLATE/enhancement_request.md
vendored
32
.github/ISSUE_TEMPLATE/enhancement_request.md
vendored
@@ -1,32 +0,0 @@
|
||||
---
|
||||
name: 🛑 Request a feature in the runner application
|
||||
about: If you have feature requests for GitHub Actions, please use the "feedback and suggestions for GitHub Actions" link below.
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
👋 You're opening a request for an enhancement in the GitHub Actions **runner application**.
|
||||
|
||||
🛑 Please stop if you're not certain that the feature you want is in the runner application - if you have a suggestion for improving GitHub Actions, please see the [GitHub Actions Feedback](https://github.com/github/feedback/discussions/categories/actions-and-packages-feedback) discussion forum which is actively monitored. Using the forum ensures that we route your problem to the correct team. 😃
|
||||
|
||||
Some additional useful links:
|
||||
* If you have found a security issue [please submit it here](https://hackerone.com/github)
|
||||
* If you have questions or issues with the service, writing workflows or actions, then please [visit the GitHub Community Forum's Actions Board](https://github.community/t5/GitHub-Actions/bd-p/actions)
|
||||
* If you are having an issue or have a question about GitHub Actions then please [contact customer support](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-github-actions#contacting-support)
|
||||
|
||||
If you have a feature request that is relevant to this repository, the runner, then please include the information below:
|
||||
-->
|
||||
|
||||
**Describe the enhancement**
|
||||
A clear and concise description of what the features or enhancement you need.
|
||||
|
||||
**Code Snippet**
|
||||
If applicable, add a code snippet.
|
||||
|
||||
**Additional information**
|
||||
Add any other context about the feature here.
|
||||
|
||||
NOTE: if the feature request has been agreed upon then the assignee will create an ADR. See docs/adrs/README.md
|
||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
# Override language selection by uncommenting this and choosing your languages
|
||||
# with:
|
||||
# languages: go, javascript, csharp, python, cpp, java
|
||||
@@ -38,4 +38,4 @@ jobs:
|
||||
working-directory: src
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
24
.github/workflows/lint.yml
vendored
Normal file
24
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Ensure full list of changed files within `super-linter`
|
||||
fetch-depth: 0
|
||||
- name: Run linters
|
||||
uses: github/super-linter@v4
|
||||
env:
|
||||
DEFAULT_BRANCH: ${{ github.base_ref }}
|
||||
EDITORCONFIG_FILE_NAME: .editorconfig
|
||||
LINTER_RULES_PATH: /src/
|
||||
VALIDATE_ALL_CODEBASE: false
|
||||
VALIDATE_CSHARP: true
|
||||
65
.github/workflows/publish-image.yml
vendored
Normal file
65
.github/workflows/publish-image.yml
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
name: Publish Runner Image
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
runnerVersion:
|
||||
type: string
|
||||
description: Version of the runner being installed
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository_owner }}/actions-runner
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Compute image version
|
||||
id: image
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const inputRunnerVersion = "${{ github.event.inputs.runnerVersion }}"
|
||||
if (inputRunnerVersion) {
|
||||
console.log(`Using input runner version ${inputRunnerVersion}`)
|
||||
core.setOutput('version', inputRunnerVersion);
|
||||
return
|
||||
}
|
||||
const runnerVersion = fs.readFileSync('${{ github.workspace }}/src/runnerversion', 'utf8').replace(/\n$/g, '')
|
||||
console.log(`Using runner version ${runnerVersion}`)
|
||||
core.setOutput('version', runnerVersion);
|
||||
|
||||
- name: Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Log into registry ${{ env.REGISTRY }}
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: build-and-push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: ./images
|
||||
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
|
||||
57
.github/workflows/release.yml
vendored
57
.github/workflows/release.yml
vendored
@@ -247,28 +247,28 @@ jobs:
|
||||
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)
|
||||
releaseNote = releaseNote.replace(/<WIN_X64_SHA>/g, '${{needs.build.outputs.win-x64-sha}}')
|
||||
releaseNote = releaseNote.replace(/<WIN_X64_SHA>/g, '${{needs.build.outputs.win-arm64-sha}}')
|
||||
releaseNote = releaseNote.replace(/<WIN_ARM64_SHA>/g, '${{needs.build.outputs.win-arm64-sha}}')
|
||||
releaseNote = releaseNote.replace(/<OSX_X64_SHA>/g, '${{needs.build.outputs.osx-x64-sha}}')
|
||||
releaseNote = releaseNote.replace(/<OSX_ARM64_SHA>/g, '${{needs.build.outputs.osx-arm64-sha}}')
|
||||
releaseNote = releaseNote.replace(/<LINUX_X64_SHA>/g, '${{needs.build.outputs.linux-x64-sha}}')
|
||||
releaseNote = releaseNote.replace(/<LINUX_ARM_SHA>/g, '${{needs.build.outputs.linux-arm-sha}}')
|
||||
releaseNote = releaseNote.replace(/<LINUX_ARM64_SHA>/g, '${{needs.build.outputs.linux-arm64-sha}}')
|
||||
releaseNote = releaseNote.replace(/<WIN_X64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.win-x64-sha-noexternals}}')
|
||||
releaseNote = releaseNote.replace(/<WIN_X64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.win-arm64-sha-noexternals}}')
|
||||
releaseNote = releaseNote.replace(/<WIN_ARM64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.win-arm64-sha-noexternals}}')
|
||||
releaseNote = releaseNote.replace(/<OSX_X64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.osx-x64-sha-noexternals}}')
|
||||
releaseNote = releaseNote.replace(/<OSX_ARM64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.osx-arm64-sha-noexternals}}')
|
||||
releaseNote = releaseNote.replace(/<LINUX_X64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.linux-x64-sha-noexternals}}')
|
||||
releaseNote = releaseNote.replace(/<LINUX_ARM_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.linux-arm-sha-noexternals}}')
|
||||
releaseNote = releaseNote.replace(/<LINUX_ARM64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.linux-arm64-sha-noexternals}}')
|
||||
releaseNote = releaseNote.replace(/<WIN_X64_SHA_NORUNTIME>/g, '${{needs.build.outputs.win-x64-sha-noruntime}}')
|
||||
releaseNote = releaseNote.replace(/<WIN_X64_SHA_NORUNTIME>/g, '${{needs.build.outputs.win-arm64-sha-noruntime}}')
|
||||
releaseNote = releaseNote.replace(/<WIN_ARM64_SHA_NORUNTIME>/g, '${{needs.build.outputs.win-arm64-sha-noruntime}}')
|
||||
releaseNote = releaseNote.replace(/<OSX_X64_SHA_NORUNTIME>/g, '${{needs.build.outputs.osx-x64-sha-noruntime}}')
|
||||
releaseNote = releaseNote.replace(/<OSX_ARM64_SHA_NORUNTIME>/g, '${{needs.build.outputs.osx-arm64-sha-noruntime}}')
|
||||
releaseNote = releaseNote.replace(/<LINUX_X64_SHA_NORUNTIME>/g, '${{needs.build.outputs.linux-x64-sha-noruntime}}')
|
||||
releaseNote = releaseNote.replace(/<LINUX_ARM_SHA_NORUNTIME>/g, '${{needs.build.outputs.linux-arm-sha-noruntime}}')
|
||||
releaseNote = releaseNote.replace(/<LINUX_ARM64_SHA_NORUNTIME>/g, '${{needs.build.outputs.linux-arm64-sha-noruntime}}')
|
||||
releaseNote = releaseNote.replace(/<WIN_X64_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.win-x64-sha-noruntime-noexternals}}')
|
||||
releaseNote = releaseNote.replace(/<WIN_X64_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.win-arm64-sha-noruntime-noexternals}}')
|
||||
releaseNote = releaseNote.replace(/<WIN_ARM64_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.win-arm64-sha-noruntime-noexternals}}')
|
||||
releaseNote = releaseNote.replace(/<OSX_X64_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.osx-x64-sha-noruntime-noexternals}}')
|
||||
releaseNote = releaseNote.replace(/<OSX_ARM64_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.osx-arm64-sha-noruntime-noexternals}}')
|
||||
releaseNote = releaseNote.replace(/<LINUX_X64_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.linux-x64-sha-noruntime-noexternals}}')
|
||||
@@ -660,3 +660,52 @@ jobs:
|
||||
asset_path: ${{ github.workspace }}/linux-arm64-trimmedpackages.json
|
||||
asset_name: actions-runner-linux-arm64-${{ steps.releaseNote.outputs.version }}-trimmedpackages.json
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
publish-image:
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository_owner }}/actions-runner
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Compute image version
|
||||
id: image
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const runnerVersion = fs.readFileSync('${{ github.workspace }}/releaseVersion', 'utf8').replace(/\n$/g, '')
|
||||
console.log(`Using runner version ${runnerVersion}`)
|
||||
core.setOutput('version', runnerVersion);
|
||||
|
||||
- name: Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Log into registry ${{ env.REGISTRY }}
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: build-and-push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: ./images
|
||||
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
|
||||
|
||||
@@ -22,4 +22,4 @@ Runner releases:
|
||||
|
||||
## Contribute
|
||||
|
||||
We accept contributions in the form of issues and pull requests. [Read more here](docs/contribute.md) before contributing.
|
||||
We accept contributions in the form of issues and pull requests. 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. [Read more about our guidelines here](docs/contribute.md) before contributing.
|
||||
|
||||
@@ -64,4 +64,4 @@ Make sure the runner has access to actions service for GitHub.com or GitHub Ente
|
||||
|
||||
## Still not working?
|
||||
|
||||
Contact [GitHub Support](https://support.github.com] if you have further questuons, or log an issue at https://github.com/actions/runner if you think it's a runner issue.
|
||||
Contact [GitHub Support](https://support.github.com) if you have further questuons, or log an issue at https://github.com/actions/runner if you think it's a runner issue.
|
||||
|
||||
@@ -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.
|
||||
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.
|
||||
|
||||
@@ -158,3 +158,11 @@ cat (Runner/Worker)_TIMESTAMP.log # view your log file
|
||||
|
||||
We use the .NET Foundation and CoreCLR style guidelines [located here](
|
||||
https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/coding-style.md)
|
||||
|
||||
### Format C# Code
|
||||
|
||||
To format both staged and unstaged .cs files
|
||||
```
|
||||
cd ./src
|
||||
./dev.(cmd|sh) format
|
||||
```
|
||||
@@ -35,7 +35,7 @@ All the configs below can be found in `.vscode/launch.json`.
|
||||
If you launch `Run` or `Run [build]`, it starts a process called `Runner.Listener`.
|
||||
This process will receive any job queued on this repository if the job runs on matching labels (e.g `runs-on: self-hosted`).
|
||||
Once a job is received, a `Runner.Listener` starts a new process of `Runner.Worker`.
|
||||
Since this is a diferent process, you can't use the same debugger session debug it.
|
||||
Since this is a different process, you can't use the same debugger session debug it.
|
||||
Instead, a parallel debugging session has to be started, using a different launch config.
|
||||
Luckily, VS Code supports multiple parallel debugging sessions.
|
||||
|
||||
|
||||
49
images/Dockerfile
Normal file
49
images/Dockerfile
Normal file
@@ -0,0 +1,49 @@
|
||||
FROM mcr.microsoft.com/dotnet/runtime-deps:6.0 as build
|
||||
|
||||
ARG RUNNER_VERSION
|
||||
ARG RUNNER_ARCH="x64"
|
||||
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.2.0
|
||||
ARG DOCKER_VERSION=20.10.23
|
||||
|
||||
RUN apt update -y && apt install curl unzip -y
|
||||
|
||||
WORKDIR /actions-runner
|
||||
RUN curl -f -L -o runner.tar.gz https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-${RUNNER_ARCH}-${RUNNER_VERSION}.tar.gz \
|
||||
&& tar xzf ./runner.tar.gz \
|
||||
&& rm runner.tar.gz
|
||||
|
||||
RUN curl -f -L -o runner-container-hooks.zip https://github.com/actions/runner-container-hooks/releases/download/v${RUNNER_CONTAINER_HOOKS_VERSION}/actions-runner-hooks-k8s-${RUNNER_CONTAINER_HOOKS_VERSION}.zip \
|
||||
&& unzip ./runner-container-hooks.zip -d ./k8s \
|
||||
&& rm runner-container-hooks.zip
|
||||
|
||||
RUN export DOCKER_ARCH=x86_64 \
|
||||
&& if [ "$RUNNER_ARCH" = "arm64" ]; then export DOCKER_ARCH=aarch64 ; fi \
|
||||
&& curl -fLo docker.tgz https://download.docker.com/linux/static/stable/${DOCKER_ARCH}/docker-${DOCKER_VERSION}.tgz \
|
||||
&& tar zxvf docker.tgz \
|
||||
&& rm -rf docker.tgz
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/runtime-deps:6.0
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV RUNNER_MANUALLY_TRAP_SIG=1
|
||||
ENV ACTIONS_RUNNER_PRINT_LOG_TO_STDOUT=1
|
||||
|
||||
RUN apt-get update -y \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
sudo \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN adduser --disabled-password --gecos "" --uid 1001 runner \
|
||||
&& groupadd docker --gid 123 \
|
||||
&& usermod -aG sudo runner \
|
||||
&& usermod -aG docker runner \
|
||||
&& echo "%sudo ALL=(ALL:ALL) NOPASSWD:ALL" > /etc/sudoers \
|
||||
&& echo "Defaults env_keep += \"DEBIAN_FRONTEND\"" >> /etc/sudoers
|
||||
|
||||
WORKDIR /home/runner
|
||||
|
||||
COPY --chown=runner:docker --from=build /actions-runner .
|
||||
|
||||
RUN install -o root -g root -m 755 docker/* /usr/bin/ && rm -rf docker
|
||||
|
||||
USER runner
|
||||
@@ -1,13 +1,19 @@
|
||||
## Features
|
||||
- Created prerelease runner package for win-arm64 architecture (#2022)
|
||||
- Added `GITHUB_STATE` and `GITHUB_OUTPUT` environment file commands (#2118)
|
||||
- Add support for ghe.com domain (#2420)
|
||||
- Add docker cli to the runner image. (#2425)
|
||||
|
||||
## Bugs
|
||||
- Fixed an issue where self hosted environments had their docker env's overwritten (#2107)
|
||||
- Fixed an issue where step summaries for composite actions got overwritten (#2077)
|
||||
- Fix URL construction bug for RunService (#2396)
|
||||
- Defer evaluation of a step's DisplayName until its condition is evaluated. (#2313)
|
||||
- Replace '(' and ')' with '[' and '] from OS.Description for fixing User-Agent header validation (#2288)
|
||||
|
||||
## Misc
|
||||
- Bumped `actions/core` dependency (#2123)
|
||||
- Bump dotnet sdk to latest version. (#2392)
|
||||
- Start calling run service for job completion (#2412, #2423)
|
||||
|
||||
_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.
|
||||
See https://docs.github.com/en/enterprise-cloud@latest/actions/hosting-your-own-runners/adding-self-hosted-runners_
|
||||
|
||||
## Windows x64
|
||||
We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows.
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.297.0
|
||||
<Update to ./src/runnerversion when creating release>
|
||||
|
||||
@@ -13,7 +13,7 @@ set -e
|
||||
|
||||
flags_found=false
|
||||
|
||||
while getopts 's:g:n:r:u:l:' opt; do
|
||||
while getopts 's:g:n:r:u:l:df' opt; do
|
||||
flags_found=true
|
||||
|
||||
case $opt in
|
||||
@@ -35,6 +35,12 @@ while getopts 's:g:n:r:u:l:' opt; do
|
||||
l)
|
||||
labels=$OPTARG
|
||||
;;
|
||||
f)
|
||||
replace='true'
|
||||
;;
|
||||
d)
|
||||
disableupdate='true'
|
||||
;;
|
||||
*)
|
||||
echo "
|
||||
Runner Service Installer
|
||||
@@ -49,7 +55,9 @@ Usage:
|
||||
-n optional name of the runner, defaults to hostname
|
||||
-r optional name of the runner group to add the runner to, defaults to the Default group
|
||||
-u optional user svc will run as, defaults to current
|
||||
-l optional list of labels (split by comma) applied on the runner"
|
||||
-l optional list of labels (split by comma) applied on the runner
|
||||
-d optional allow runner to remain on the current version for one month after the release of a newer version
|
||||
-f optional replace any existing runner with the same name"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
@@ -169,8 +177,8 @@ fi
|
||||
|
||||
echo
|
||||
echo "Configuring ${runner_name} @ $runner_url"
|
||||
echo "./config.sh --unattended --url $runner_url --token *** --name $runner_name ${labels:+--labels $labels} ${runner_group:+--runnergroup \"$runner_group\"}"
|
||||
sudo -E -u ${svc_user} ./config.sh --unattended --url $runner_url --token $RUNNER_TOKEN --name $runner_name ${labels:+--labels $labels} ${runner_group:+--runnergroup "$runner_group"}
|
||||
echo "./config.sh --unattended --url $runner_url --token *** --name $runner_name ${labels:+--labels $labels} ${runner_group:+--runnergroup \"$runner_group\"} ${disableupdate:+--disableupdate}"
|
||||
sudo -E -u ${svc_user} ./config.sh --unattended --url $runner_url --token $RUNNER_TOKEN ${replace:+--replace} --name $runner_name ${labels:+--labels $labels} ${runner_group:+--runnergroup "$runner_group"} ${disableupdate:+--disableupdate}
|
||||
|
||||
#---------------------------------------
|
||||
# Configuring as a service
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#/bin/bash
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
@@ -12,7 +12,7 @@ set -e
|
||||
#
|
||||
# Usage:
|
||||
# export RUNNER_CFG_PAT=<yourPAT>
|
||||
# ./delete.sh scope name
|
||||
# ./delete.sh <scope> [<name>]
|
||||
#
|
||||
# scope required repo (:owner/:repo) or org (:organization)
|
||||
# name optional defaults to hostname. name to delete
|
||||
@@ -26,17 +26,17 @@ set -e
|
||||
runner_scope=${1}
|
||||
runner_name=${2}
|
||||
|
||||
echo "Deleting runner ${runner_name} @ ${runner_scope}"
|
||||
|
||||
function fatal()
|
||||
function fatal()
|
||||
{
|
||||
echo "error: $1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ -z "${runner_scope}" ]; then fatal "supply scope as argument 1"; fi
|
||||
if [ -z "${runner_name}" ]; then fatal "supply name as argument 2"; fi
|
||||
if [ -z "${RUNNER_CFG_PAT}" ]; then fatal "RUNNER_CFG_PAT must be set before calling"; fi
|
||||
if [ -z "${runner_name}" ]; then runner_name=`hostname`; fi
|
||||
|
||||
echo "Deleting runner ${runner_name} @ ${runner_scope}"
|
||||
|
||||
which curl || fatal "curl required. Please install in PATH with apt-get, brew, etc"
|
||||
which jq || fatal "jq required. Please install in PATH with apt-get, brew, etc"
|
||||
|
||||
@@ -1 +1 @@
|
||||
1d709d93e5d3c6c6c656a61aa6c1781050224788a05b0e6ecc4c3c0408bdf89c
|
||||
39f2a931565d6a10e695ac8ed14bb9dcbb568151410349b32dbf9c27bae29602
|
||||
@@ -1 +1 @@
|
||||
b92a47cfeaad02255b1f7a377060651b73ae5e5db22a188dbbcb4183ab03a03d
|
||||
29ffb303537d8ba674fbebc7729292c21c4ebd17b3198f91ed593ef4cbbb67b5
|
||||
@@ -1 +1 @@
|
||||
68a9a8ef0843a8bb74241894f6f63fd76241a82295c5337d3cc7a940a314c78e
|
||||
de6868a836fa3cb9e5ddddbc079da1c25e819aa2d2fc193cc9931c353687c57c
|
||||
@@ -1 +1 @@
|
||||
02c7126ff4d63ee2a0ae390c81434c125630522aadf35903bbeebb1a99d8af99
|
||||
339d3e1a5fd28450c0fe6cb820cc7aae291f0f9e2d153ac34e1f7b080e35d30e
|
||||
@@ -1 +1 @@
|
||||
c9d5a542f8d765168855a89e83ae0a8970d00869041c4f9a766651c04c72b212
|
||||
dcb7f606c1d7d290381e5020ee73e7f16dcbd2f20ac9b431362ccbb5120d449c
|
||||
@@ -1 +1 @@
|
||||
39d0683f0f115a211cb10c473e9574c16549a19d4e9a6c637ded3d7022bf809f
|
||||
1bbcb0e9a2cf4be4b1fce77458de139b70ac58efcbb415a6db028b9373ae1673
|
||||
@@ -1 +1 @@
|
||||
d94f2fbaf210297162bc9f3add819d73682c3aa6899e321c3872412b924d5504
|
||||
44cd25f3c104d0abb44d262397a80e0b2c4f206465c5d899a22eec043dac0fb3
|
||||
2
src/Misc/contentHash/externals/linux-arm
vendored
2
src/Misc/contentHash/externals/linux-arm
vendored
@@ -1 +1 @@
|
||||
6ed30a2c1ee403a610d63e82bb230b9ba846a9c25cec9e4ea8672fb6ed4e1a51
|
||||
3807dcbf947e840c33535fb466b096d76bf09e5c0254af8fc8cbbb24c6388222
|
||||
2
src/Misc/contentHash/externals/linux-arm64
vendored
2
src/Misc/contentHash/externals/linux-arm64
vendored
@@ -1 +1 @@
|
||||
711c30c51ec52c9b7a9a2eb399d6ab2ab5ee1dc72de11879f2f36f919f163d78
|
||||
ee01eee80cd8a460a4b9780ee13fdd20f25c59e754b4ccd99df55fbba2a85634
|
||||
2
src/Misc/contentHash/externals/linux-x64
vendored
2
src/Misc/contentHash/externals/linux-x64
vendored
@@ -1 +1 @@
|
||||
a49479ca4b4988a06c097e8d22c51fd08a11c13f40807366236213d0e008cf6a
|
||||
a9fb9c14e24e79aec97d4da197dd7bfc6364297d6fce573afb2df48cc9a931f8
|
||||
2
src/Misc/contentHash/externals/osx-arm64
vendored
2
src/Misc/contentHash/externals/osx-arm64
vendored
@@ -1 +1 @@
|
||||
cc4708962a80325de0baa5ae8484e0cb9ae976ac6a4178c1c0d448b8c52bd7f7
|
||||
a4e0e8fc62eba0967a39c7d693dcd0aeb8b2bed0765f9c38df80d42884f65341
|
||||
2
src/Misc/contentHash/externals/osx-x64
vendored
2
src/Misc/contentHash/externals/osx-x64
vendored
@@ -1 +1 @@
|
||||
8e97df75230b843462a9b4c578ccec604ee4b4a1066120c85b04374317fa372b
|
||||
17ac17fbe785b3d6fa2868d8d17185ebfe0c90b4b0ddf6b67eac70e42bcd989b
|
||||
2
src/Misc/contentHash/externals/win-arm64
vendored
2
src/Misc/contentHash/externals/win-arm64
vendored
@@ -1 +1 @@
|
||||
e5dace2d41cc0682d096dcce4970079ad48ec7107e46195970eecfdb3df2acef
|
||||
89f24657a550f1e818b0e9975e5b80edcf4dd22b7d4bccbb9e48e37f45d30fb1
|
||||
|
||||
2
src/Misc/contentHash/externals/win-x64
vendored
2
src/Misc/contentHash/externals/win-x64
vendored
@@ -1 +1 @@
|
||||
f75a671e5a188c76680739689aa75331a2c09d483dce9c80023518c48fd67a18
|
||||
24fd131b5dce33ef16038b771407bc0507da8682a72fb3b7780607235f76db0b
|
||||
43
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
43
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
@@ -14,7 +14,7 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^12.7.12",
|
||||
"@typescript-eslint/parser": "^5.15.0",
|
||||
"@zeit/ncc": "^0.20.5",
|
||||
"@vercel/ncc": "^0.36.0",
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-plugin-github": "^4.3.5",
|
||||
"prettier": "^1.19.1",
|
||||
@@ -346,11 +346,10 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@zeit/ncc": {
|
||||
"version": "0.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@zeit/ncc/-/ncc-0.20.5.tgz",
|
||||
"integrity": "sha512-XU6uzwvv95DqxciQx+aOLhbyBx/13ky+RK1y88Age9Du3BlA4mMPCy13BGjayOrrumOzlq1XV3SD/BWiZENXlw==",
|
||||
"deprecated": "@zeit/ncc is no longer maintained. Please use @vercel/ncc instead.",
|
||||
"node_modules/@vercel/ncc": {
|
||||
"version": "0.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.36.0.tgz",
|
||||
"integrity": "sha512-/ZTUJ/ZkRt694k7KJNimgmHjtQcRuVwsST2Z6XfYveQIuBbHR+EqkTc1jfgPkQmMyk/vtpxo3nVxe8CNuau86A==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"ncc": "dist/ncc/cli.js"
|
||||
@@ -1722,9 +1721,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
@@ -1824,9 +1823,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
@@ -2747,10 +2746,10 @@
|
||||
"eslint-visitor-keys": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"@zeit/ncc": {
|
||||
"version": "0.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@zeit/ncc/-/ncc-0.20.5.tgz",
|
||||
"integrity": "sha512-XU6uzwvv95DqxciQx+aOLhbyBx/13ky+RK1y88Age9Du3BlA4mMPCy13BGjayOrrumOzlq1XV3SD/BWiZENXlw==",
|
||||
"@vercel/ncc": {
|
||||
"version": "0.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.36.0.tgz",
|
||||
"integrity": "sha512-/ZTUJ/ZkRt694k7KJNimgmHjtQcRuVwsST2Z6XfYveQIuBbHR+EqkTc1jfgPkQmMyk/vtpxo3nVxe8CNuau86A==",
|
||||
"dev": true
|
||||
},
|
||||
"acorn": {
|
||||
@@ -3756,9 +3755,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
@@ -3840,9 +3839,9 @@
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^12.7.12",
|
||||
"@typescript-eslint/parser": "^5.15.0",
|
||||
"@zeit/ncc": "^0.20.5",
|
||||
"@vercel/ncc": "^0.36.0",
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-plugin-github": "^4.3.5",
|
||||
"prettier": "^1.19.1",
|
||||
|
||||
3
src/Misc/expressionFunc/hashFilesV2/.eslintignore
Normal file
3
src/Misc/expressionFunc/hashFilesV2/.eslintignore
Normal file
@@ -0,0 +1,3 @@
|
||||
dist/
|
||||
lib/
|
||||
node_modules/
|
||||
61
src/Misc/expressionFunc/hashFilesV2/.eslintrc.json
Normal file
61
src/Misc/expressionFunc/hashFilesV2/.eslintrc.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"extends": ["plugin:github/recommended"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 9,
|
||||
"sourceType": "module",
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"rules": {
|
||||
"eslint-comments/no-use": "off",
|
||||
"import/no-namespace": "off",
|
||||
"no-console": "off",
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
|
||||
"@typescript-eslint/no-require-imports": "error",
|
||||
"@typescript-eslint/array-type": "error",
|
||||
"@typescript-eslint/await-thenable": "error",
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"error",
|
||||
{
|
||||
"selector": "default",
|
||||
"format": ["camelCase"]
|
||||
}
|
||||
],
|
||||
"camelcase": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
|
||||
"@typescript-eslint/func-call-spacing": ["error", "never"],
|
||||
"@typescript-eslint/no-array-constructor": "error",
|
||||
"@typescript-eslint/no-empty-interface": "error",
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
"@typescript-eslint/no-extraneous-class": "error",
|
||||
"@typescript-eslint/no-for-in-array": "error",
|
||||
"@typescript-eslint/no-inferrable-types": "error",
|
||||
"@typescript-eslint/no-misused-new": "error",
|
||||
"@typescript-eslint/no-namespace": "error",
|
||||
"@typescript-eslint/no-non-null-assertion": "warn",
|
||||
"@typescript-eslint/no-unnecessary-qualifier": "error",
|
||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||
"@typescript-eslint/no-useless-constructor": "error",
|
||||
"@typescript-eslint/no-var-requires": "error",
|
||||
"@typescript-eslint/prefer-for-of": "warn",
|
||||
"@typescript-eslint/prefer-function-type": "warn",
|
||||
"@typescript-eslint/prefer-includes": "error",
|
||||
"@typescript-eslint/prefer-string-starts-ends-with": "error",
|
||||
"@typescript-eslint/promise-function-async": "error",
|
||||
"@typescript-eslint/require-array-sort-compare": "error",
|
||||
"@typescript-eslint/restrict-plus-operands": "error",
|
||||
"@typescript-eslint/semi": ["error", "never"],
|
||||
"@typescript-eslint/type-annotation-spacing": "error",
|
||||
"@typescript-eslint/unbound-method": "error",
|
||||
"filenames/match-regex" : "off",
|
||||
"github/no-then" : 1, // warning
|
||||
"semi": "off"
|
||||
},
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true
|
||||
}
|
||||
}
|
||||
3
src/Misc/expressionFunc/hashFilesV2/.prettierignore
Normal file
3
src/Misc/expressionFunc/hashFilesV2/.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
||||
dist/
|
||||
lib/
|
||||
node_modules/
|
||||
11
src/Misc/expressionFunc/hashFilesV2/.prettierrc.json
Normal file
11
src/Misc/expressionFunc/hashFilesV2/.prettierrc.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"bracketSpacing": false,
|
||||
"arrowParens": "avoid",
|
||||
"parser": "typescript"
|
||||
}
|
||||
4
src/Misc/expressionFunc/hashFilesV2/README.md
Normal file
4
src/Misc/expressionFunc/hashFilesV2/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
To compile this package (output will be stored in `Misc/layoutbin`) run `npm install && npm run all`.
|
||||
|
||||
> Note: this package also needs to be recompiled for dependabot PRs updating one of
|
||||
> its dependencies.
|
||||
5525
src/Misc/expressionFunc/hashFilesV2/package-lock.json
generated
Normal file
5525
src/Misc/expressionFunc/hashFilesV2/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
36
src/Misc/expressionFunc/hashFilesV2/package.json
Normal file
36
src/Misc/expressionFunc/hashFilesV2/package.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "hashFiles",
|
||||
"version": "1.0.0",
|
||||
"description": "GitHub Actions HashFiles() expression function",
|
||||
"main": "lib/hashFiles.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"format": "prettier --write **/*.ts",
|
||||
"format-check": "prettier --check **/*.ts",
|
||||
"lint": "eslint src/**/*.ts",
|
||||
"pack": "ncc build -o ../../layoutbin/hashFilesV2",
|
||||
"all": "npm run build && npm run format && npm run lint && npm run pack"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/actions/runner.git"
|
||||
},
|
||||
"keywords": [
|
||||
"actions"
|
||||
],
|
||||
"author": "GitHub Actions",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/glob": "^0.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^12.7.12",
|
||||
"@typescript-eslint/parser": "^5.15.0",
|
||||
"@vercel/ncc": "^0.36.0",
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-plugin-github": "^4.3.5",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"prettier": "^2.8.4",
|
||||
"typescript": "^3.6.4"
|
||||
}
|
||||
}
|
||||
31
src/Misc/expressionFunc/hashFilesV2/src/hashFiles.ts
Normal file
31
src/Misc/expressionFunc/hashFilesV2/src/hashFiles.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import * as glob from '@actions/glob'
|
||||
|
||||
async function run(): Promise<void> {
|
||||
// arg0 -> node
|
||||
// arg1 -> hashFiles.js
|
||||
// env[followSymbolicLinks] = true/null
|
||||
// env[patterns] -> glob patterns
|
||||
let followSymbolicLinks = false
|
||||
const matchPatterns = process.env.patterns || ''
|
||||
if (process.env.followSymbolicLinks === 'true') {
|
||||
console.log('Follow symbolic links')
|
||||
followSymbolicLinks = true
|
||||
}
|
||||
const githubWorkspace = process.cwd()
|
||||
console.log(`Match Pattern: ${matchPatterns}`)
|
||||
try {
|
||||
const result = await glob.hashFiles(
|
||||
matchPatterns,
|
||||
githubWorkspace,
|
||||
{followSymbolicLinks},
|
||||
true
|
||||
)
|
||||
console.error(`__OUTPUT__${result}__OUTPUT__`)
|
||||
process.exit(0)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
run()
|
||||
12
src/Misc/expressionFunc/hashFilesV2/tsconfig.json
Normal file
12
src/Misc/expressionFunc/hashFilesV2/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
"outDir": "./lib", /* Redirect output structure to the directory. */
|
||||
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
},
|
||||
"exclude": ["node_modules", "**/*.test.ts"]
|
||||
}
|
||||
@@ -5,7 +5,7 @@ PRECACHE=$2
|
||||
NODE_URL=https://nodejs.org/dist
|
||||
UNOFFICIAL_NODE_URL=https://unofficial-builds.nodejs.org/download/release
|
||||
NODE12_VERSION="12.22.7"
|
||||
NODE16_VERSION="16.13.0"
|
||||
NODE16_VERSION="16.16.0"
|
||||
|
||||
get_abs_path() {
|
||||
# exploits the fact that pwd will print abs path when no args
|
||||
|
||||
@@ -24,7 +24,8 @@ if (exitServiceAfterNFailures <= 0) {
|
||||
exitServiceAfterNFailures = NaN;
|
||||
}
|
||||
|
||||
var consecutiveFailureCount = 0;
|
||||
var unknownFailureRetryCount = 0;
|
||||
var retriableFailureRetryCount = 0;
|
||||
|
||||
var gracefulShutdown = function () {
|
||||
console.log("Shutting down runner listener");
|
||||
@@ -62,7 +63,8 @@ var runService = function () {
|
||||
|
||||
listener.stdout.on("data", (data) => {
|
||||
if (data.toString("utf8").includes("Listening for Jobs")) {
|
||||
consecutiveFailureCount = 0;
|
||||
unknownFailureRetryCount = 0;
|
||||
retriableFailureRetryCount = 0;
|
||||
}
|
||||
process.stdout.write(data.toString("utf8"));
|
||||
});
|
||||
@@ -92,24 +94,38 @@ var runService = function () {
|
||||
console.log(
|
||||
"Runner listener exit with retryable error, re-launch runner in 5 seconds."
|
||||
);
|
||||
consecutiveFailureCount = 0;
|
||||
unknownFailureRetryCount = 0;
|
||||
retriableFailureRetryCount++;
|
||||
if (retriableFailureRetryCount >= 10) {
|
||||
console.error(
|
||||
"Stopping the runner after 10 consecutive re-tryable failures"
|
||||
);
|
||||
stopping = true;
|
||||
}
|
||||
} else if (code === 3 || code === 4) {
|
||||
console.log(
|
||||
"Runner listener exit because of updating, re-launch runner in 5 seconds."
|
||||
);
|
||||
consecutiveFailureCount = 0;
|
||||
unknownFailureRetryCount = 0;
|
||||
retriableFailureRetryCount++;
|
||||
if (retriableFailureRetryCount >= 10) {
|
||||
console.error(
|
||||
"Stopping the runner after 10 consecutive re-tryable failures"
|
||||
);
|
||||
stopping = true;
|
||||
}
|
||||
} else {
|
||||
var messagePrefix = "Runner listener exit with undefined return code";
|
||||
consecutiveFailureCount++;
|
||||
unknownFailureRetryCount++;
|
||||
retriableFailureRetryCount = 0;
|
||||
if (
|
||||
!isNaN(exitServiceAfterNFailures) &&
|
||||
consecutiveFailureCount >= exitServiceAfterNFailures
|
||||
unknownFailureRetryCount >= exitServiceAfterNFailures
|
||||
) {
|
||||
console.error(
|
||||
`${messagePrefix}, exiting service after ${consecutiveFailureCount} consecutive failures`
|
||||
`${messagePrefix}, exiting service after ${unknownFailureRetryCount} consecutive failures`
|
||||
);
|
||||
gracefulShutdown();
|
||||
return;
|
||||
stopping = true
|
||||
} else {
|
||||
console.log(`${messagePrefix}, re-launch runner in 5 seconds.`);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
5342
src/Misc/layoutbin/hashFilesV2/index.js
Normal file
5342
src/Misc/layoutbin/hashFilesV2/index.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,20 @@ while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symli
|
||||
done
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
|
||||
# Wait for docker to start
|
||||
if [ ! -z "$RUNNER_WAIT_FOR_DOCKER_IN_SECONDS" ]; then
|
||||
if [ "$RUNNER_WAIT_FOR_DOCKER_IN_SECONDS" -gt 0 ]; then
|
||||
echo "Waiting for docker to be ready."
|
||||
for i in $(seq "$RUNNER_WAIT_FOR_DOCKER_IN_SECONDS"); do
|
||||
if docker ps > /dev/null 2>&1; then
|
||||
echo "Docker is ready."
|
||||
break
|
||||
fi
|
||||
"$DIR"/safe_sleep.sh 1
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
updateFile="update.finished"
|
||||
"$DIR"/bin/Runner.Listener run $*
|
||||
|
||||
|
||||
@@ -9,16 +9,52 @@ while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symli
|
||||
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
|
||||
done
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
# run the helper process which keep the listener alive
|
||||
while :;
|
||||
do
|
||||
cp -f "$DIR"/run-helper.sh.template "$DIR"/run-helper.sh
|
||||
"$DIR"/run-helper.sh $*
|
||||
returnCode=$?
|
||||
if [[ $returnCode -eq 2 ]]; then
|
||||
echo "Restarting runner..."
|
||||
else
|
||||
echo "Exiting runner..."
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
run() {
|
||||
# run the helper process which keep the listener alive
|
||||
while :;
|
||||
do
|
||||
cp -f "$DIR"/run-helper.sh.template "$DIR"/run-helper.sh
|
||||
"$DIR"/run-helper.sh $*
|
||||
returnCode=$?
|
||||
if [[ $returnCode -eq 2 ]]; then
|
||||
echo "Restarting runner..."
|
||||
else
|
||||
echo "Exiting runner..."
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
runWithManualTrap() {
|
||||
# Set job control
|
||||
set -m
|
||||
|
||||
trap 'kill -INT -$PID' INT TERM
|
||||
|
||||
# run the helper process which keep the listener alive
|
||||
while :;
|
||||
do
|
||||
cp -f "$DIR"/run-helper.sh.template "$DIR"/run-helper.sh
|
||||
"$DIR"/run-helper.sh $* &
|
||||
PID=$!
|
||||
wait -f $PID
|
||||
returnCode=$?
|
||||
if [[ $returnCode -eq 2 ]]; then
|
||||
echo "Restarting runner..."
|
||||
else
|
||||
echo "Exiting runner..."
|
||||
# Unregister signal handling before exit
|
||||
trap - INT TERM
|
||||
# wait for last parts to be logged
|
||||
wait $PID
|
||||
exit $returnCode
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
if [[ -z "$RUNNER_MANUALLY_TRAP_SIG" ]]; then
|
||||
run $*
|
||||
else
|
||||
runWithManualTrap $*
|
||||
fi
|
||||
@@ -4,6 +4,7 @@ checkScripts/downloadCert.js
|
||||
checkScripts/makeWebRequest.js
|
||||
darwin.svc.sh.template
|
||||
hashFiles/index.js
|
||||
hashFilesV2/index.js
|
||||
installdependencies.sh
|
||||
macos-run-invoker.js
|
||||
Microsoft.IdentityModel.Logging.dll
|
||||
|
||||
@@ -74,6 +74,7 @@ Microsoft.Win32.Registry.dll
|
||||
mscordaccore.dll
|
||||
mscordaccore_amd64_amd64_6.0.522.21309.dll
|
||||
mscordaccore_arm64_arm64_6.0.522.21309.dll
|
||||
mscordaccore_amd64_amd64_6.0.1322.58009.dll
|
||||
mscordbi.dll
|
||||
mscorlib.dll
|
||||
mscorrc.debug.dll
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace GitHub.Runner.Common
|
||||
new EscapeMapping(token: "%", replacement: "%25"),
|
||||
};
|
||||
|
||||
private readonly Dictionary<string, string> _properties = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, string> _properties = new(StringComparer.OrdinalIgnoreCase);
|
||||
public const string Prefix = "##[";
|
||||
public const string _commandKey = "::";
|
||||
|
||||
|
||||
51
src/Runner.Common/ActionsRunServer.cs
Normal file
51
src/Runner.Common/ActionsRunServer.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.Pipelines;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.WebApi;
|
||||
|
||||
namespace GitHub.Runner.Common
|
||||
{
|
||||
[ServiceLocator(Default = typeof(ActionsRunServer))]
|
||||
public interface IActionsRunServer : IRunnerService
|
||||
{
|
||||
Task ConnectAsync(Uri serverUrl, VssCredentials credentials);
|
||||
|
||||
Task<AgentJobRequestMessage> GetJobMessageAsync(string id, CancellationToken token);
|
||||
}
|
||||
|
||||
public sealed class ActionsRunServer : RunnerService, IActionsRunServer
|
||||
{
|
||||
private bool _hasConnection;
|
||||
private VssConnection _connection;
|
||||
private TaskAgentHttpClient _taskAgentClient;
|
||||
|
||||
public async Task ConnectAsync(Uri serverUrl, VssCredentials credentials)
|
||||
{
|
||||
_connection = await EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(100));
|
||||
_taskAgentClient = _connection.GetClient<TaskAgentHttpClient>();
|
||||
_hasConnection = true;
|
||||
}
|
||||
|
||||
private void CheckConnection()
|
||||
{
|
||||
if (!_hasConnection)
|
||||
{
|
||||
throw new InvalidOperationException($"SetConnection");
|
||||
}
|
||||
}
|
||||
|
||||
public Task<AgentJobRequestMessage> GetJobMessageAsync(string id, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckConnection();
|
||||
var jobMessage = RetryRequest<AgentJobRequestMessage>(async () =>
|
||||
{
|
||||
return await _taskAgentClient.GetJobMessageAsync(id, cancellationToken);
|
||||
}, cancellationToken);
|
||||
|
||||
return jobMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,17 +74,18 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
get
|
||||
{
|
||||
Uri accountUri = new Uri(this.ServerUrl);
|
||||
Uri accountUri = new(this.ServerUrl);
|
||||
string repoOrOrgName = string.Empty;
|
||||
|
||||
if (accountUri.Host.EndsWith(".githubusercontent.com", StringComparison.OrdinalIgnoreCase))
|
||||
if (accountUri.Host.EndsWith(".githubusercontent.com", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(this.GitHubUrl))
|
||||
{
|
||||
Uri gitHubUrl = new Uri(this.GitHubUrl);
|
||||
Uri gitHubUrl = new(this.GitHubUrl);
|
||||
|
||||
// Use the "NWO part" from the GitHub URL path
|
||||
repoOrOrgName = gitHubUrl.AbsolutePath.Trim('/');
|
||||
}
|
||||
else
|
||||
|
||||
if (string.IsNullOrEmpty(repoOrOrgName))
|
||||
{
|
||||
repoOrOrgName = accountUri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace GitHub.Runner.Common
|
||||
{
|
||||
@@ -90,7 +90,6 @@ namespace GitHub.Runner.Common
|
||||
public static class Args
|
||||
{
|
||||
public static readonly string Auth = "auth";
|
||||
public static readonly string JitConfig = "jitconfig";
|
||||
public static readonly string Labels = "labels";
|
||||
public static readonly string MonitorSocketAddress = "monitorsocketaddress";
|
||||
public static readonly string Name = "name";
|
||||
@@ -105,11 +104,13 @@ namespace GitHub.Runner.Common
|
||||
public static readonly string Token = "token";
|
||||
public static readonly string PAT = "pat";
|
||||
public static readonly string WindowsLogonPassword = "windowslogonpassword";
|
||||
public static readonly string JitConfig = "jitconfig";
|
||||
public static string[] Secrets => new[]
|
||||
{
|
||||
PAT,
|
||||
Token,
|
||||
WindowsLogonPassword,
|
||||
JitConfig,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -128,7 +129,9 @@ namespace GitHub.Runner.Common
|
||||
public static readonly string Check = "check";
|
||||
public static readonly string Commit = "commit";
|
||||
public static readonly string Ephemeral = "ephemeral";
|
||||
public static readonly string GenerateServiceConfig = "generateServiceConfig";
|
||||
public static readonly string Help = "help";
|
||||
public static readonly string Local = "local";
|
||||
public static readonly string Replace = "replace";
|
||||
public static readonly string DisableUpdate = "disableupdate";
|
||||
public static readonly string Once = "once"; // Keep this around since customers still relies on it
|
||||
@@ -153,16 +156,21 @@ namespace GitHub.Runner.Common
|
||||
public static readonly string Node12Warning = "DistributedTask.AddWarningToNode12Action";
|
||||
public static readonly string UseContainerPathForTemplate = "DistributedTask.UseContainerPathForTemplate";
|
||||
public static readonly string AllowRunnerContainerHooks = "DistributedTask.AllowRunnerContainerHooks";
|
||||
public static readonly string UseGlobHashFiles = "DistributedTask.UseGlobHashFiles";
|
||||
}
|
||||
|
||||
public static readonly string InternalTelemetryIssueDataKey = "_internal_telemetry";
|
||||
public static readonly Guid TelemetryRecordId = new Guid("11111111-1111-1111-1111-111111111111");
|
||||
public static readonly string WorkerCrash = "WORKER_CRASH";
|
||||
public static readonly string LowDiskSpace = "LOW_DISK_SPACE";
|
||||
public static readonly string UnsupportedCommand = "UNSUPPORTED_COMMAND";
|
||||
public static readonly string ResultsUploadFailure = "RESULTS_UPLOAD_FAILURE";
|
||||
public static readonly string UnsupportedCommandMessage = "The `{0}` command is deprecated and will be disabled soon. Please upgrade to using Environment Files. For more information see: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/";
|
||||
public static readonly string UnsupportedCommandMessageDisabled = "The `{0}` command is disabled. Please upgrade to using Environment Files or opt into unsecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_COMMANDS` environment variable to `true`. For more information see: https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/";
|
||||
public static readonly string UnsupportedStopCommandTokenDisabled = "You cannot use a endToken that is an empty string, the string 'pause-logging', or another workflow command. For more information see: https://docs.github.com/actions/learn-github-actions/workflow-commands-for-github-actions#example-stopping-and-starting-workflow-commands or opt into insecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_STOPCOMMAND_TOKENS` environment variable to `true`.";
|
||||
public static readonly string UnsupportedSummarySize = "$GITHUB_STEP_SUMMARY upload aborted, supports content up to a size of {0}k, got {1}k. For more information see: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summary";
|
||||
public static readonly string Node12DetectedAfterEndOfLife = "Node.js 12 actions are deprecated. For more information see: https://github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/. Please update the following actions to use Node.js 16: {0}";
|
||||
public static readonly string SummaryUploadError = "$GITHUB_STEP_SUMMARY upload aborted, an error occurred when uploading the summary. For more information see: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summary";
|
||||
public static readonly string Node12DetectedAfterEndOfLife = "Node.js 12 actions are deprecated. Please update the following actions to use Node.js 16: {0}. For more information see: https://github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/.";
|
||||
}
|
||||
|
||||
public static class RunnerEvent
|
||||
@@ -243,6 +251,7 @@ namespace GitHub.Runner.Common
|
||||
// Set this env var to "node12" to downgrade the node version for internal functions (e.g hashfiles). This does NOT affect the version of node actions.
|
||||
public static readonly string ForcedInternalNodeVersion = "ACTIONS_RUNNER_FORCED_INTERNAL_NODE_VERSION";
|
||||
public static readonly string ForcedActionsNodeVersion = "ACTIONS_RUNNER_FORCE_ACTIONS_NODE_VERSION";
|
||||
public static readonly string PrintLogToStdout = "ACTIONS_RUNNER_PRINT_LOG_TO_STDOUT";
|
||||
}
|
||||
|
||||
public static class System
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace GitHub.Runner.Common
|
||||
|
||||
public sealed class ExtensionManager : RunnerService, IExtensionManager
|
||||
{
|
||||
private readonly ConcurrentDictionary<Type, List<IExtension>> _cache = new ConcurrentDictionary<Type, List<IExtension>>();
|
||||
private readonly ConcurrentDictionary<Type, List<IExtension>> _cache = new();
|
||||
|
||||
public List<T> GetExtensions<T>() where T : class, IExtension
|
||||
{
|
||||
|
||||
@@ -51,12 +51,12 @@ namespace GitHub.Runner.Common
|
||||
private static int _defaultLogRetentionDays = 30;
|
||||
private static int[] _vssHttpMethodEventIds = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 24 };
|
||||
private static int[] _vssHttpCredentialEventIds = new int[] { 11, 13, 14, 15, 16, 17, 18, 20, 21, 22, 27, 29 };
|
||||
private readonly ConcurrentDictionary<Type, object> _serviceInstances = new ConcurrentDictionary<Type, object>();
|
||||
private readonly ConcurrentDictionary<Type, Type> _serviceTypes = new ConcurrentDictionary<Type, Type>();
|
||||
private readonly ConcurrentDictionary<Type, object> _serviceInstances = new();
|
||||
private readonly ConcurrentDictionary<Type, Type> _serviceTypes = new();
|
||||
private readonly ISecretMasker _secretMasker = new SecretMasker();
|
||||
private readonly List<ProductInfoHeaderValue> _userAgents = new List<ProductInfoHeaderValue>() { new ProductInfoHeaderValue($"GitHubActionsRunner-{BuildConstants.RunnerPackage.PackageName}", BuildConstants.RunnerPackage.Version) };
|
||||
private CancellationTokenSource _runnerShutdownTokenSource = new CancellationTokenSource();
|
||||
private object _perfLock = new object();
|
||||
private readonly List<ProductInfoHeaderValue> _userAgents = new() { new ProductInfoHeaderValue($"GitHubActionsRunner-{BuildConstants.RunnerPackage.PackageName}", BuildConstants.RunnerPackage.Version) };
|
||||
private CancellationTokenSource _runnerShutdownTokenSource = new();
|
||||
private object _perfLock = new();
|
||||
private Tracing _trace;
|
||||
private Tracing _actionsHttpTrace;
|
||||
private Tracing _netcoreHttpTrace;
|
||||
@@ -66,7 +66,7 @@ namespace GitHub.Runner.Common
|
||||
private IDisposable _diagListenerSubscription;
|
||||
private StartupType _startupType;
|
||||
private string _perfFile;
|
||||
private RunnerWebProxy _webProxy = new RunnerWebProxy();
|
||||
private RunnerWebProxy _webProxy = new();
|
||||
|
||||
public event EventHandler Unloading;
|
||||
public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token;
|
||||
@@ -94,6 +94,13 @@ namespace GitHub.Runner.Common
|
||||
this.SecretMasker.AddValueEncoder(ValueEncoders.PowerShellPreAmpersandEscape);
|
||||
this.SecretMasker.AddValueEncoder(ValueEncoders.PowerShellPostAmpersandEscape);
|
||||
|
||||
// Create StdoutTraceListener if ENV is set
|
||||
StdoutTraceListener stdoutTraceListener = null;
|
||||
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable(Constants.Variables.Agent.PrintLogToStdout)))
|
||||
{
|
||||
stdoutTraceListener = new StdoutTraceListener(hostType);
|
||||
}
|
||||
|
||||
// Create the trace manager.
|
||||
if (string.IsNullOrEmpty(logFile))
|
||||
{
|
||||
@@ -113,11 +120,11 @@ namespace GitHub.Runner.Common
|
||||
|
||||
// this should give us _diag folder under runner root directory
|
||||
string diagLogDirectory = Path.Combine(new DirectoryInfo(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)).Parent.FullName, Constants.Path.DiagDirectory);
|
||||
_traceManager = new TraceManager(new HostTraceListener(diagLogDirectory, hostType, logPageSize, logRetentionDays), this.SecretMasker);
|
||||
_traceManager = new TraceManager(new HostTraceListener(diagLogDirectory, hostType, logPageSize, logRetentionDays), stdoutTraceListener, this.SecretMasker);
|
||||
}
|
||||
else
|
||||
{
|
||||
_traceManager = new TraceManager(new HostTraceListener(logFile), this.SecretMasker);
|
||||
_traceManager = new TraceManager(new HostTraceListener(logFile), stdoutTraceListener, this.SecretMasker);
|
||||
}
|
||||
|
||||
_trace = GetTrace(nameof(HostContext));
|
||||
@@ -219,6 +226,20 @@ namespace GitHub.Runner.Common
|
||||
}
|
||||
|
||||
_userAgents.Add(new ProductInfoHeaderValue("CommitSHA", BuildConstants.Source.CommitHash));
|
||||
|
||||
var extraUserAgent = Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_EXTRA_USER_AGENT");
|
||||
if (!string.IsNullOrEmpty(extraUserAgent))
|
||||
{
|
||||
var extraUserAgentSplit = extraUserAgent.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (extraUserAgentSplit.Length != 2)
|
||||
{
|
||||
_trace.Error($"GITHUB_ACTIONS_RUNNER_EXTRA_USER_AGENT is not in the format of 'name/version'.");
|
||||
}
|
||||
|
||||
var extraUserAgentHeader = new ProductInfoHeaderValue(extraUserAgentSplit[0], extraUserAgentSplit[1]);
|
||||
_trace.Info($"Adding extra user agent '{extraUserAgentHeader}' to all HTTP requests.");
|
||||
_userAgents.Add(extraUserAgentHeader);
|
||||
}
|
||||
}
|
||||
|
||||
public string GetDirectory(WellKnownDirectory directory)
|
||||
|
||||
@@ -164,7 +164,7 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
if (_enableLogRetention)
|
||||
{
|
||||
DirectoryInfo diags = new DirectoryInfo(_logFileDirectory);
|
||||
DirectoryInfo diags = new(_logFileDirectory);
|
||||
var logs = diags.GetFiles($"{_logFilePrefix}*.log");
|
||||
foreach (var log in logs)
|
||||
{
|
||||
|
||||
@@ -13,6 +13,8 @@ using GitHub.Runner.Sdk;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.WebApi;
|
||||
using GitHub.Services.WebApi.Utilities.Internal;
|
||||
using GitHub.Services.Results.Client;
|
||||
using GitHub.Services.OAuth;
|
||||
|
||||
namespace GitHub.Runner.Common
|
||||
{
|
||||
@@ -22,11 +24,15 @@ namespace GitHub.Runner.Common
|
||||
Task ConnectAsync(VssConnection jobConnection);
|
||||
|
||||
void InitializeWebsocketClient(ServiceEndpoint serviceEndpoint);
|
||||
void InitializeResultsClient(Uri uri, string token);
|
||||
|
||||
// logging and console
|
||||
Task<TaskLog> AppendLogContentAsync(Guid scopeIdentifier, string hubName, Guid planId, int logId, Stream uploadStream, CancellationToken cancellationToken);
|
||||
Task AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, long? startLine, CancellationToken cancellationToken);
|
||||
Task<TaskAttachment> CreateAttachmentAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, String type, String name, Stream uploadStream, CancellationToken cancellationToken);
|
||||
Task CreateStepSummaryAsync(string planId, string jobId, Guid stepId, string file, CancellationToken cancellationToken);
|
||||
Task CreateResultsStepLogAsync(string planId, string jobId, Guid stepId, string file, bool finalize, bool firstBlock, long lineCount, CancellationToken cancellationToken);
|
||||
Task CreateResultsJobLogAsync(string planId, string jobId, string file, bool finalize, bool firstBlock, long lineCount, CancellationToken cancellationToken);
|
||||
Task<TaskLog> CreateLogAsync(Guid scopeIdentifier, string hubName, Guid planId, TaskLog log, CancellationToken cancellationToken);
|
||||
Task<Timeline> CreateTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken);
|
||||
Task<List<TimelineRecord>> UpdateTimelineRecordsAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, IEnumerable<TimelineRecord> records, CancellationToken cancellationToken);
|
||||
@@ -40,6 +46,7 @@ namespace GitHub.Runner.Common
|
||||
private bool _hasConnection;
|
||||
private VssConnection _connection;
|
||||
private TaskHttpClient _taskClient;
|
||||
private ResultsHttpClient _resultsClient;
|
||||
private ClientWebSocket _websocketClient;
|
||||
|
||||
private ServiceEndpoint _serviceEndpoint;
|
||||
@@ -143,6 +150,12 @@ namespace GitHub.Runner.Common
|
||||
InitializeWebsocketClient(TimeSpan.Zero);
|
||||
}
|
||||
|
||||
public void InitializeResultsClient(Uri uri, string token)
|
||||
{
|
||||
var httpMessageHandler = HostContext.CreateHttpClientHandler();
|
||||
this._resultsClient = new ResultsHttpClient(uri, httpMessageHandler, token, disposeHandler: true);
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
CloseWebSocket(WebSocketCloseStatus.NormalClosure, CancellationToken.None);
|
||||
@@ -305,6 +318,33 @@ namespace GitHub.Runner.Common
|
||||
return _taskClient.CreateAttachmentAsync(scopeIdentifier, hubName, planId, timelineId, timelineRecordId, type, name, uploadStream, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task CreateStepSummaryAsync(string planId, string jobId, Guid stepId, string file, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_resultsClient != null)
|
||||
{
|
||||
return _resultsClient.UploadStepSummaryAsync(planId, jobId, stepId, file, cancellationToken: cancellationToken);
|
||||
}
|
||||
throw new InvalidOperationException("Results client is not initialized.");
|
||||
}
|
||||
|
||||
public Task CreateResultsStepLogAsync(string planId, string jobId, Guid stepId, string file, bool finalize, bool firstBlock, long lineCount, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_resultsClient != null)
|
||||
{
|
||||
return _resultsClient.UploadResultsStepLogAsync(planId, jobId, stepId, file, finalize, firstBlock, lineCount, cancellationToken: cancellationToken);
|
||||
}
|
||||
throw new InvalidOperationException("Results client is not initialized.");
|
||||
}
|
||||
|
||||
public Task CreateResultsJobLogAsync(string planId, string jobId, string file, bool finalize, bool firstBlock, long lineCount, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_resultsClient != null)
|
||||
{
|
||||
return _resultsClient.UploadResultsJobLogAsync(planId, jobId, file, finalize, firstBlock, lineCount, cancellationToken: cancellationToken);
|
||||
}
|
||||
throw new InvalidOperationException("Results client is not initialized.");
|
||||
}
|
||||
|
||||
public Task<TaskLog> CreateLogAsync(Guid scopeIdentifier, string hubName, Guid planId, TaskLog log, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckConnection();
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace GitHub.Runner.Common
|
||||
void Start(Pipelines.AgentJobRequestMessage jobRequest);
|
||||
void QueueWebConsoleLine(Guid stepRecordId, string line, long? lineNumber = null);
|
||||
void QueueFileUpload(Guid timelineId, Guid timelineRecordId, string type, string name, string path, bool deleteSource);
|
||||
void QueueResultsUpload(Guid timelineRecordId, string name, string path, string type, bool deleteSource, bool finalize, bool firstBlock, long totalLines);
|
||||
void QueueTimelineRecordUpdate(Guid timelineId, TimelineRecord timelineRecord);
|
||||
}
|
||||
|
||||
@@ -30,6 +31,7 @@ namespace GitHub.Runner.Common
|
||||
private static readonly TimeSpan _delayForWebConsoleLineDequeue = TimeSpan.FromMilliseconds(500);
|
||||
private static readonly TimeSpan _delayForTimelineUpdateDequeue = TimeSpan.FromMilliseconds(500);
|
||||
private static readonly TimeSpan _delayForFileUploadDequeue = TimeSpan.FromMilliseconds(1000);
|
||||
private static readonly TimeSpan _delayForResultsUploadDequeue = TimeSpan.FromMilliseconds(1000);
|
||||
|
||||
// Job message information
|
||||
private Guid _scopeIdentifier;
|
||||
@@ -39,30 +41,33 @@ namespace GitHub.Runner.Common
|
||||
private Guid _jobTimelineRecordId;
|
||||
|
||||
// queue for web console line
|
||||
private readonly ConcurrentQueue<ConsoleLineInfo> _webConsoleLineQueue = new ConcurrentQueue<ConsoleLineInfo>();
|
||||
private readonly ConcurrentQueue<ConsoleLineInfo> _webConsoleLineQueue = new();
|
||||
|
||||
// queue for file upload (log file or attachment)
|
||||
private readonly ConcurrentQueue<UploadFileInfo> _fileUploadQueue = new ConcurrentQueue<UploadFileInfo>();
|
||||
private readonly ConcurrentQueue<UploadFileInfo> _fileUploadQueue = new();
|
||||
|
||||
private readonly ConcurrentQueue<ResultsUploadFileInfo> _resultsFileUploadQueue = new();
|
||||
|
||||
// queue for timeline or timeline record update (one queue per timeline)
|
||||
private readonly ConcurrentDictionary<Guid, ConcurrentQueue<TimelineRecord>> _timelineUpdateQueue = new ConcurrentDictionary<Guid, ConcurrentQueue<TimelineRecord>>();
|
||||
private readonly ConcurrentDictionary<Guid, ConcurrentQueue<TimelineRecord>> _timelineUpdateQueue = new();
|
||||
|
||||
// indicate how many timelines we have, we will process _timelineUpdateQueue base on the order of timeline in this list
|
||||
private readonly List<Guid> _allTimelines = new List<Guid>();
|
||||
private readonly List<Guid> _allTimelines = new();
|
||||
|
||||
// bufferd timeline records that fail to update
|
||||
private readonly Dictionary<Guid, List<TimelineRecord>> _bufferedRetryRecords = new Dictionary<Guid, List<TimelineRecord>>();
|
||||
private readonly Dictionary<Guid, List<TimelineRecord>> _bufferedRetryRecords = new();
|
||||
|
||||
// Task for each queue's dequeue process
|
||||
private Task _webConsoleLineDequeueTask;
|
||||
private Task _fileUploadDequeueTask;
|
||||
private Task _resultsUploadDequeueTask;
|
||||
private Task _timelineUpdateDequeueTask;
|
||||
|
||||
// common
|
||||
private IJobServer _jobServer;
|
||||
private Task[] _allDequeueTasks;
|
||||
private readonly TaskCompletionSource<int> _jobCompletionSource = new TaskCompletionSource<int>();
|
||||
private readonly TaskCompletionSource<int> _jobRecordUpdated = new TaskCompletionSource<int>();
|
||||
private readonly TaskCompletionSource<int> _jobCompletionSource = new();
|
||||
private readonly TaskCompletionSource<int> _jobRecordUpdated = new();
|
||||
private bool _queueInProcess = false;
|
||||
|
||||
public TaskCompletionSource<int> JobRecordUpdated => _jobRecordUpdated;
|
||||
@@ -79,6 +84,9 @@ namespace GitHub.Runner.Common
|
||||
private bool _webConsoleLineAggressiveDequeue = true;
|
||||
private bool _firstConsoleOutputs = true;
|
||||
|
||||
private bool _resultsClientInitiated = false;
|
||||
private delegate Task ResultsFileUploadHandler(ResultsUploadFileInfo file);
|
||||
|
||||
public override void Initialize(IHostContext hostContext)
|
||||
{
|
||||
base.Initialize(hostContext);
|
||||
@@ -93,6 +101,20 @@ namespace GitHub.Runner.Common
|
||||
|
||||
_jobServer.InitializeWebsocketClient(serviceEndPoint);
|
||||
|
||||
// This code is usually wrapped by an instance of IExecutionContext which isn't available here.
|
||||
jobRequest.Variables.TryGetValue("system.github.results_endpoint", out VariableValue resultsEndpointVariable);
|
||||
var resultsReceiverEndpoint = resultsEndpointVariable?.Value;
|
||||
|
||||
if (serviceEndPoint?.Authorization != null &&
|
||||
serviceEndPoint.Authorization.Parameters.TryGetValue("AccessToken", out var accessToken) &&
|
||||
!string.IsNullOrEmpty(accessToken) &&
|
||||
!string.IsNullOrEmpty(resultsReceiverEndpoint))
|
||||
{
|
||||
Trace.Info("Initializing results client");
|
||||
_jobServer.InitializeResultsClient(new Uri(resultsReceiverEndpoint), accessToken);
|
||||
_resultsClientInitiated = true;
|
||||
}
|
||||
|
||||
if (_queueInProcess)
|
||||
{
|
||||
Trace.Info("No-opt, all queue process tasks are running.");
|
||||
@@ -120,10 +142,13 @@ namespace GitHub.Runner.Common
|
||||
Trace.Info("Start process file upload queue.");
|
||||
_fileUploadDequeueTask = ProcessFilesUploadQueueAsync();
|
||||
|
||||
Trace.Info("Start results file upload queue.");
|
||||
_resultsUploadDequeueTask = ProcessResultsUploadQueueAsync();
|
||||
|
||||
Trace.Info("Start process timeline update queue.");
|
||||
_timelineUpdateDequeueTask = ProcessTimelinesUpdateQueueAsync();
|
||||
|
||||
_allDequeueTasks = new Task[] { _webConsoleLineDequeueTask, _fileUploadDequeueTask, _timelineUpdateDequeueTask };
|
||||
_allDequeueTasks = new Task[] { _webConsoleLineDequeueTask, _fileUploadDequeueTask, _timelineUpdateDequeueTask, _resultsUploadDequeueTask };
|
||||
_queueInProcess = true;
|
||||
}
|
||||
|
||||
@@ -154,6 +179,10 @@ namespace GitHub.Runner.Common
|
||||
await ProcessFilesUploadQueueAsync(runOnce: true);
|
||||
Trace.Info("File upload queue drained.");
|
||||
|
||||
Trace.Verbose("Draining results upload queue.");
|
||||
await ProcessResultsUploadQueueAsync(runOnce: true);
|
||||
Trace.Info("Results upload queue drained.");
|
||||
|
||||
// ProcessTimelinesUpdateQueueAsync() will throw exception during shutdown
|
||||
// if there is any timeline records that failed to update contains output variabls.
|
||||
Trace.Verbose("Draining timeline update queue.");
|
||||
@@ -204,6 +233,45 @@ namespace GitHub.Runner.Common
|
||||
_fileUploadQueue.Enqueue(newFile);
|
||||
}
|
||||
|
||||
public void QueueResultsUpload(Guid timelineRecordId, string name, string path, string type, bool deleteSource, bool finalize, bool firstBlock, long totalLines)
|
||||
{
|
||||
if (!_resultsClientInitiated)
|
||||
{
|
||||
Trace.Verbose("Skipping results upload");
|
||||
try
|
||||
{
|
||||
if (deleteSource)
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Info("Catch exception during delete skipped results upload file.");
|
||||
Trace.Error(ex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// all parameter not null, file path exist.
|
||||
var newFile = new ResultsUploadFileInfo()
|
||||
{
|
||||
Name = name,
|
||||
Path = path,
|
||||
Type = type,
|
||||
PlanId = _planId.ToString(),
|
||||
JobId = _jobTimelineRecordId.ToString(),
|
||||
RecordId = timelineRecordId,
|
||||
DeleteSource = deleteSource,
|
||||
Finalize = finalize,
|
||||
FirstBlock = firstBlock,
|
||||
TotalLines = totalLines,
|
||||
};
|
||||
|
||||
Trace.Verbose("Enqueue results file upload queue: file '{0}' attach to job {1} step {2}", newFile.Path, _jobTimelineRecordId, timelineRecordId);
|
||||
_resultsFileUploadQueue.Enqueue(newFile);
|
||||
}
|
||||
|
||||
public void QueueTimelineRecordUpdate(Guid timelineId, TimelineRecord timelineRecord)
|
||||
{
|
||||
ArgUtil.NotEmpty(timelineId, nameof(timelineId));
|
||||
@@ -237,8 +305,8 @@ namespace GitHub.Runner.Common
|
||||
}
|
||||
|
||||
// Group consolelines by timeline record of each step
|
||||
Dictionary<Guid, List<TimelineRecordLogLine>> stepsConsoleLines = new Dictionary<Guid, List<TimelineRecordLogLine>>();
|
||||
List<Guid> stepRecordIds = new List<Guid>(); // We need to keep lines in order
|
||||
Dictionary<Guid, List<TimelineRecordLogLine>> stepsConsoleLines = new();
|
||||
List<Guid> stepRecordIds = new(); // We need to keep lines in order
|
||||
int linesCounter = 0;
|
||||
ConsoleLineInfo lineInfo;
|
||||
while (_webConsoleLineQueue.TryDequeue(out lineInfo))
|
||||
@@ -264,7 +332,7 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
// Split consolelines into batch, each batch will container at most 100 lines.
|
||||
int batchCounter = 0;
|
||||
List<List<TimelineRecordLogLine>> batchedLines = new List<List<TimelineRecordLogLine>>();
|
||||
List<List<TimelineRecordLogLine>> batchedLines = new();
|
||||
foreach (var line in stepsConsoleLines[stepRecordId])
|
||||
{
|
||||
var currentBatch = batchedLines.ElementAtOrDefault(batchCounter);
|
||||
@@ -299,7 +367,7 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
try
|
||||
{
|
||||
// Give at most 60s for each request.
|
||||
// Give at most 60s for each request.
|
||||
using (var timeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(60)))
|
||||
{
|
||||
await _jobServer.AppendTimelineRecordFeedAsync(_scopeIdentifier, _hubName, _planId, _jobTimelineId, _jobTimelineRecordId, stepRecordId, batch.Select(logLine => logLine.Line).ToList(), batch[0].LineNumber, timeoutTokenSource.Token);
|
||||
@@ -338,7 +406,7 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
while (!_jobCompletionSource.Task.IsCompleted || runOnce)
|
||||
{
|
||||
List<UploadFileInfo> filesToUpload = new List<UploadFileInfo>();
|
||||
List<UploadFileInfo> filesToUpload = new();
|
||||
UploadFileInfo dequeueFile;
|
||||
while (_fileUploadQueue.TryDequeue(out dequeueFile))
|
||||
{
|
||||
@@ -394,17 +462,97 @@ namespace GitHub.Runner.Common
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessResultsUploadQueueAsync(bool runOnce = false)
|
||||
{
|
||||
Trace.Info("Starting results-based upload queue...");
|
||||
|
||||
while (!_jobCompletionSource.Task.IsCompleted || runOnce)
|
||||
{
|
||||
List<ResultsUploadFileInfo> filesToUpload = new();
|
||||
ResultsUploadFileInfo dequeueFile;
|
||||
while (_resultsFileUploadQueue.TryDequeue(out dequeueFile))
|
||||
{
|
||||
filesToUpload.Add(dequeueFile);
|
||||
// process at most 10 file uploads.
|
||||
if (!runOnce && filesToUpload.Count > 10)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (filesToUpload.Count > 0)
|
||||
{
|
||||
if (runOnce)
|
||||
{
|
||||
Trace.Info($"Uploading {filesToUpload.Count} file(s) in one shot through results service.");
|
||||
}
|
||||
|
||||
int errorCount = 0;
|
||||
foreach (var file in filesToUpload)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (String.Equals(file.Type, ChecksAttachmentType.StepSummary, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await UploadSummaryFile(file);
|
||||
}
|
||||
else if (String.Equals(file.Type, CoreAttachmentType.ResultsLog, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (file.RecordId != _jobTimelineRecordId)
|
||||
{
|
||||
Trace.Info($"Got a step log file to send to results service.");
|
||||
await UploadResultsStepLogFile(file);
|
||||
}
|
||||
else if (file.RecordId == _jobTimelineRecordId)
|
||||
{
|
||||
Trace.Info($"Got a job log file to send to results service.");
|
||||
await UploadResultsJobLogFile(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var issue = new Issue() { Type = IssueType.Warning, Message = $"Caught exception during file upload to results. {ex.Message}" };
|
||||
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.ResultsUploadFailure;
|
||||
|
||||
var telemetryRecord = new TimelineRecord()
|
||||
{
|
||||
Id = Constants.Runner.TelemetryRecordId,
|
||||
};
|
||||
telemetryRecord.Issues.Add(issue);
|
||||
QueueTimelineRecordUpdate(_jobTimelineId, telemetryRecord);
|
||||
|
||||
Trace.Info("Catch exception during file upload to results, keep going since the process is best effort.");
|
||||
Trace.Error(ex);
|
||||
errorCount++;
|
||||
}
|
||||
}
|
||||
|
||||
Trace.Info("Tried to upload {0} file(s) to results, success rate: {1}/{0}.", filesToUpload.Count, filesToUpload.Count - errorCount);
|
||||
}
|
||||
|
||||
if (runOnce)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Delay(_delayForResultsUploadDequeue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessTimelinesUpdateQueueAsync(bool runOnce = false)
|
||||
{
|
||||
while (!_jobCompletionSource.Task.IsCompleted || runOnce)
|
||||
{
|
||||
List<PendingTimelineRecord> pendingUpdates = new List<PendingTimelineRecord>();
|
||||
List<PendingTimelineRecord> pendingUpdates = new();
|
||||
foreach (var timeline in _allTimelines)
|
||||
{
|
||||
ConcurrentQueue<TimelineRecord> recordQueue;
|
||||
if (_timelineUpdateQueue.TryGetValue(timeline, out recordQueue))
|
||||
{
|
||||
List<TimelineRecord> records = new List<TimelineRecord>();
|
||||
List<TimelineRecord> records = new();
|
||||
TimelineRecord record;
|
||||
while (recordQueue.TryDequeue(out record))
|
||||
{
|
||||
@@ -426,7 +574,7 @@ namespace GitHub.Runner.Common
|
||||
// we need track whether we have new sub-timeline been created on the last run.
|
||||
// if so, we need continue update timeline record even we on the last run.
|
||||
bool pendingSubtimelineUpdate = false;
|
||||
List<Exception> mainTimelineRecordsUpdateErrors = new List<Exception>();
|
||||
List<Exception> mainTimelineRecordsUpdateErrors = new();
|
||||
if (pendingUpdates.Count > 0)
|
||||
{
|
||||
foreach (var update in pendingUpdates)
|
||||
@@ -529,7 +677,7 @@ namespace GitHub.Runner.Common
|
||||
return timelineRecords;
|
||||
}
|
||||
|
||||
Dictionary<Guid, TimelineRecord> dict = new Dictionary<Guid, TimelineRecord>();
|
||||
Dictionary<Guid, TimelineRecord> dict = new();
|
||||
foreach (TimelineRecord rec in timelineRecords)
|
||||
{
|
||||
if (rec == null)
|
||||
@@ -665,6 +813,64 @@ namespace GitHub.Runner.Common
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UploadSummaryFile(ResultsUploadFileInfo file)
|
||||
{
|
||||
Trace.Info($"Starting to upload summary file to results service {file.Name}, {file.Path}");
|
||||
ResultsFileUploadHandler summaryHandler = async (file) =>
|
||||
{
|
||||
await _jobServer.CreateStepSummaryAsync(file.PlanId, file.JobId, file.RecordId, file.Path, CancellationToken.None);
|
||||
};
|
||||
|
||||
await UploadResultsFile(file, summaryHandler);
|
||||
}
|
||||
|
||||
private async Task UploadResultsStepLogFile(ResultsUploadFileInfo file)
|
||||
{
|
||||
Trace.Info($"Starting upload of step log file to results service {file.Name}, {file.Path}");
|
||||
ResultsFileUploadHandler stepLogHandler = async (file) =>
|
||||
{
|
||||
await _jobServer.CreateResultsStepLogAsync(file.PlanId, file.JobId, file.RecordId, file.Path, file.Finalize, file.FirstBlock, file.TotalLines, CancellationToken.None);
|
||||
};
|
||||
|
||||
await UploadResultsFile(file, stepLogHandler);
|
||||
}
|
||||
|
||||
private async Task UploadResultsJobLogFile(ResultsUploadFileInfo file)
|
||||
{
|
||||
Trace.Info($"Starting upload of job log file to results service {file.Name}, {file.Path}");
|
||||
ResultsFileUploadHandler jobLogHandler = async (file) =>
|
||||
{
|
||||
await _jobServer.CreateResultsJobLogAsync(file.PlanId, file.JobId, file.Path, file.Finalize, file.FirstBlock, file.TotalLines, CancellationToken.None);
|
||||
};
|
||||
|
||||
await UploadResultsFile(file, jobLogHandler);
|
||||
}
|
||||
|
||||
private async Task UploadResultsFile(ResultsUploadFileInfo file, ResultsFileUploadHandler uploadHandler)
|
||||
{
|
||||
bool uploadSucceed = false;
|
||||
try
|
||||
{
|
||||
await uploadHandler(file);
|
||||
uploadSucceed = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (uploadSucceed && file.DeleteSource)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(file.Path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Info("Exception encountered during deletion of a temporary file that was already successfully uploaded to results.");
|
||||
Trace.Error(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class PendingTimelineRecord
|
||||
@@ -683,6 +889,21 @@ namespace GitHub.Runner.Common
|
||||
public bool DeleteSource { get; set; }
|
||||
}
|
||||
|
||||
internal class ResultsUploadFileInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string Path { get; set; }
|
||||
public string PlanId { get; set; }
|
||||
public string JobId { get; set; }
|
||||
public Guid RecordId { get; set; }
|
||||
public bool DeleteSource { get; set; }
|
||||
public bool Finalize { get; set; }
|
||||
public bool FirstBlock { get; set; }
|
||||
public long TotalLines { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal class ConsoleLineInfo
|
||||
{
|
||||
|
||||
@@ -21,6 +21,12 @@ namespace GitHub.Runner.Common
|
||||
// 8 MB
|
||||
public const int PageSize = 8 * 1024 * 1024;
|
||||
|
||||
// For Results
|
||||
public static string BlocksFolder = "blocks";
|
||||
|
||||
// 2 MB
|
||||
public const int BlockSize = 2 * 1024 * 1024;
|
||||
|
||||
private Guid _timelineId;
|
||||
private Guid _timelineRecordId;
|
||||
private FileStream _pageData;
|
||||
@@ -32,6 +38,13 @@ namespace GitHub.Runner.Common
|
||||
private string _pagesFolder;
|
||||
private IJobServerQueue _jobServerQueue;
|
||||
|
||||
private string _resultsDataFileName;
|
||||
private FileStream _resultsBlockData;
|
||||
private StreamWriter _resultsBlockWriter;
|
||||
private string _resultsBlockFolder;
|
||||
private int _blockByteCount;
|
||||
private int _blockCount;
|
||||
|
||||
public long TotalLines => _totalLines;
|
||||
|
||||
public override void Initialize(IHostContext hostContext)
|
||||
@@ -39,8 +52,10 @@ namespace GitHub.Runner.Common
|
||||
base.Initialize(hostContext);
|
||||
_totalLines = 0;
|
||||
_pagesFolder = Path.Combine(hostContext.GetDirectory(WellKnownDirectory.Diag), PagingFolder);
|
||||
_jobServerQueue = HostContext.GetService<IJobServerQueue>();
|
||||
Directory.CreateDirectory(_pagesFolder);
|
||||
_resultsBlockFolder = Path.Combine(hostContext.GetDirectory(WellKnownDirectory.Diag), BlocksFolder);
|
||||
Directory.CreateDirectory(_resultsBlockFolder);
|
||||
_jobServerQueue = HostContext.GetService<IJobServerQueue>();
|
||||
}
|
||||
|
||||
public void Setup(Guid timelineId, Guid timelineRecordId)
|
||||
@@ -60,11 +75,17 @@ namespace GitHub.Runner.Common
|
||||
// lazy creation on write
|
||||
if (_pageWriter == null)
|
||||
{
|
||||
Create();
|
||||
NewPage();
|
||||
}
|
||||
|
||||
if (_resultsBlockWriter == null)
|
||||
{
|
||||
NewBlock();
|
||||
}
|
||||
|
||||
string line = $"{DateTime.UtcNow.ToString("O")} {message}";
|
||||
_pageWriter.WriteLine(line);
|
||||
_resultsBlockWriter.WriteLine(line);
|
||||
|
||||
_totalLines++;
|
||||
if (line.IndexOf('\n') != -1)
|
||||
@@ -78,21 +99,25 @@ namespace GitHub.Runner.Common
|
||||
}
|
||||
}
|
||||
|
||||
_byteCount += System.Text.Encoding.UTF8.GetByteCount(line);
|
||||
var bytes = System.Text.Encoding.UTF8.GetByteCount(line);
|
||||
_byteCount += bytes;
|
||||
_blockByteCount += bytes;
|
||||
if (_byteCount >= PageSize)
|
||||
{
|
||||
NewPage();
|
||||
}
|
||||
|
||||
if (_blockByteCount >= BlockSize)
|
||||
{
|
||||
NewBlock();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void End()
|
||||
{
|
||||
EndPage();
|
||||
}
|
||||
|
||||
private void Create()
|
||||
{
|
||||
NewPage();
|
||||
EndBlock(true);
|
||||
}
|
||||
|
||||
private void NewPage()
|
||||
@@ -117,5 +142,27 @@ namespace GitHub.Runner.Common
|
||||
_jobServerQueue.QueueFileUpload(_timelineId, _timelineRecordId, "DistributedTask.Core.Log", "CustomToolLog", _dataFileName, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void NewBlock()
|
||||
{
|
||||
EndBlock(false);
|
||||
_blockByteCount = 0;
|
||||
_resultsDataFileName = Path.Combine(_resultsBlockFolder, $"{_timelineId}_{_timelineRecordId}.{++_blockCount}");
|
||||
_resultsBlockData = new FileStream(_resultsDataFileName, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
_resultsBlockWriter = new StreamWriter(_resultsBlockData, System.Text.Encoding.UTF8);
|
||||
}
|
||||
|
||||
private void EndBlock(bool finalize)
|
||||
{
|
||||
if (_resultsBlockWriter != null)
|
||||
{
|
||||
_resultsBlockWriter.Flush();
|
||||
_resultsBlockData.Flush();
|
||||
_resultsBlockWriter.Dispose();
|
||||
_resultsBlockWriter = null;
|
||||
_resultsBlockData = null;
|
||||
_jobServerQueue.QueueResultsUpload(_timelineRecordId, "ResultsLog", _resultsDataFileName, "Results.Core.Log", deleteSource: true, finalize, firstBlock: _resultsDataFileName.EndsWith(".1"), totalLines: _totalLines);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace GitHub.Runner.Common
|
||||
|
||||
public async Task<WorkerMessage> ReceiveAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
WorkerMessage result = new WorkerMessage(MessageType.NotInitialized, string.Empty);
|
||||
WorkerMessage result = new(MessageType.NotInitialized, string.Empty);
|
||||
result.MessageType = (MessageType)await _readStream.ReadInt32Async(cancellationToken);
|
||||
result.Body = await _readStream.ReadStringAsync(cancellationToken);
|
||||
Trace.Info($"Receiving message of length {result.Body.Length}, with hash '{IOUtil.GetSha256Hash(result.Body)}'");
|
||||
|
||||
@@ -291,7 +291,7 @@ namespace GitHub.Runner.Common
|
||||
public static string GetEnvironmentVariable(this Process process, IHostContext hostContext, string variable)
|
||||
{
|
||||
var trace = hostContext.GetTrace(nameof(LinuxProcessExtensions));
|
||||
Dictionary<string, string> env = new Dictionary<string, string>();
|
||||
Dictionary<string, string> env = new();
|
||||
|
||||
if (Directory.Exists("/proc"))
|
||||
{
|
||||
@@ -322,8 +322,8 @@ namespace GitHub.Runner.Common
|
||||
// It doesn't escape '=' or ' ', so we can't parse the output into a dictionary of all envs.
|
||||
// So we only look for the env you request, in the format of variable=value. (it won't work if you variable contains = or space)
|
||||
trace.Info($"Read env from output of `ps e -p {process.Id} -o command`");
|
||||
List<string> psOut = new List<string>();
|
||||
object outputLock = new object();
|
||||
List<string> psOut = new();
|
||||
object outputLock = new();
|
||||
using (var p = hostContext.CreateService<IProcessInvoker>())
|
||||
{
|
||||
p.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Actions.RunService.WebApi;
|
||||
using GitHub.DistributedTask.Pipelines;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.WebApi;
|
||||
using Sdk.RSWebApi.Contracts;
|
||||
using Sdk.WebApi.WebApi.RawClient;
|
||||
|
||||
namespace GitHub.Runner.Common
|
||||
{
|
||||
@@ -15,47 +18,28 @@ namespace GitHub.Runner.Common
|
||||
Task ConnectAsync(Uri serverUrl, VssCredentials credentials);
|
||||
|
||||
Task<AgentJobRequestMessage> GetJobMessageAsync(string id, CancellationToken token);
|
||||
|
||||
Task CompleteJobAsync(Guid planId, Guid jobId, TaskResult result, Dictionary<String, VariableValue> outputs, IList<StepResult> stepResults, CancellationToken token);
|
||||
|
||||
Task<RenewJobResponse> RenewJobAsync(Guid planId, Guid jobId, CancellationToken token);
|
||||
}
|
||||
|
||||
public sealed class RunServer : RunnerService, IRunServer
|
||||
{
|
||||
private bool _hasConnection;
|
||||
private VssConnection _connection;
|
||||
private TaskAgentHttpClient _taskAgentClient;
|
||||
private Uri requestUri;
|
||||
private RawConnection _connection;
|
||||
private RunServiceHttpClient _runServiceHttpClient;
|
||||
|
||||
public async Task ConnectAsync(Uri serverUrl, VssCredentials credentials)
|
||||
public async Task ConnectAsync(Uri serverUri, VssCredentials credentials)
|
||||
{
|
||||
_connection = await EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(100));
|
||||
_taskAgentClient = _connection.GetClient<TaskAgentHttpClient>();
|
||||
requestUri = serverUri;
|
||||
|
||||
_connection = VssUtil.CreateRawConnection(serverUri, credentials);
|
||||
_runServiceHttpClient = await _connection.GetClientAsync<RunServiceHttpClient>();
|
||||
_hasConnection = true;
|
||||
}
|
||||
|
||||
private async Task<VssConnection> EstablishVssConnection(Uri serverUrl, VssCredentials credentials, TimeSpan timeout)
|
||||
{
|
||||
Trace.Info($"EstablishVssConnection");
|
||||
Trace.Info($"Establish connection with {timeout.TotalSeconds} seconds timeout.");
|
||||
int attemptCount = 5;
|
||||
while (attemptCount-- > 0)
|
||||
{
|
||||
var connection = VssUtil.CreateConnection(serverUrl, credentials, timeout: timeout);
|
||||
try
|
||||
{
|
||||
await connection.ConnectAsync();
|
||||
return connection;
|
||||
}
|
||||
catch (Exception ex) when (attemptCount > 0)
|
||||
{
|
||||
Trace.Info($"Catch exception during connect. {attemptCount} attempt left.");
|
||||
Trace.Error(ex);
|
||||
|
||||
await HostContext.Delay(TimeSpan.FromMilliseconds(100), CancellationToken.None);
|
||||
}
|
||||
}
|
||||
|
||||
// should never reach here.
|
||||
throw new InvalidOperationException(nameof(EstablishVssConnection));
|
||||
}
|
||||
|
||||
private void CheckConnection()
|
||||
{
|
||||
if (!_hasConnection)
|
||||
@@ -67,37 +51,34 @@ namespace GitHub.Runner.Common
|
||||
public Task<AgentJobRequestMessage> GetJobMessageAsync(string id, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckConnection();
|
||||
var jobMessage = RetryRequest<AgentJobRequestMessage>(async () =>
|
||||
{
|
||||
return await _taskAgentClient.GetJobMessageAsync(id, cancellationToken);
|
||||
}, cancellationToken);
|
||||
var jobMessage = RetryRequest<AgentJobRequestMessage>(
|
||||
async () => await _runServiceHttpClient.GetJobMessageAsync(requestUri, id, cancellationToken), cancellationToken);
|
||||
if (jobMessage == null)
|
||||
{
|
||||
throw new TaskOrchestrationJobNotFoundException(id);
|
||||
}
|
||||
|
||||
return jobMessage;
|
||||
}
|
||||
|
||||
private async Task<T> RetryRequest<T>(Func<Task<T>> func,
|
||||
CancellationToken cancellationToken,
|
||||
int maxRetryAttemptsCount = 5
|
||||
)
|
||||
public Task CompleteJobAsync(Guid planId, Guid jobId, TaskResult result, Dictionary<String, VariableValue> outputs, IList<StepResult> stepResults, CancellationToken cancellationToken)
|
||||
{
|
||||
var retryCount = 0;
|
||||
while (true)
|
||||
CheckConnection();
|
||||
return RetryRequest(
|
||||
async () => await _runServiceHttpClient.CompleteJobAsync(requestUri, planId, jobId, result, outputs, stepResults, cancellationToken), cancellationToken);
|
||||
}
|
||||
|
||||
public Task<RenewJobResponse> RenewJobAsync(Guid planId, Guid jobId, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckConnection();
|
||||
var renewJobResponse = RetryRequest<RenewJobResponse>(
|
||||
async () => await _runServiceHttpClient.RenewJobAsync(requestUri, planId, jobId, cancellationToken), cancellationToken);
|
||||
if (renewJobResponse == null)
|
||||
{
|
||||
retryCount++;
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
try
|
||||
{
|
||||
return await func();
|
||||
}
|
||||
// TODO: Add handling of non-retriable exceptions: https://github.com/github/actions-broker/issues/122
|
||||
catch (Exception ex) when (retryCount < maxRetryAttemptsCount)
|
||||
{
|
||||
Trace.Error("Catch exception during get full job message");
|
||||
Trace.Error(ex);
|
||||
var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15));
|
||||
Trace.Warning($"Back off {backOff.TotalSeconds} seconds before next retry. {maxRetryAttemptsCount - retryCount} attempt left.");
|
||||
await Task.Delay(backOff, cancellationToken);
|
||||
}
|
||||
throw new TaskOrchestrationJobNotFoundException(jobId.ToString());
|
||||
}
|
||||
|
||||
return renewJobResponse;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace GitHub.Runner.Common
|
||||
Task<TaskAgentSession> CreateAgentSessionAsync(Int32 poolId, TaskAgentSession session, CancellationToken cancellationToken);
|
||||
Task DeleteAgentMessageAsync(Int32 poolId, Int64 messageId, Guid sessionId, CancellationToken cancellationToken);
|
||||
Task DeleteAgentSessionAsync(Int32 poolId, Guid sessionId, CancellationToken cancellationToken);
|
||||
Task<TaskAgentMessage> GetAgentMessageAsync(Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, CancellationToken cancellationToken);
|
||||
Task<TaskAgentMessage> GetAgentMessageAsync(Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, string runnerVersion, CancellationToken cancellationToken);
|
||||
|
||||
// job request
|
||||
Task<TaskAgentJobRequest> GetAgentRequestAsync(int poolId, long requestId, CancellationToken cancellationToken);
|
||||
@@ -179,31 +179,6 @@ namespace GitHub.Runner.Common
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<VssConnection> EstablishVssConnection(Uri serverUrl, VssCredentials credentials, TimeSpan timeout)
|
||||
{
|
||||
Trace.Info($"Establish connection with {timeout.TotalSeconds} seconds timeout.");
|
||||
int attemptCount = 5;
|
||||
while (attemptCount-- > 0)
|
||||
{
|
||||
var connection = VssUtil.CreateConnection(serverUrl, credentials, timeout: timeout);
|
||||
try
|
||||
{
|
||||
await connection.ConnectAsync();
|
||||
return connection;
|
||||
}
|
||||
catch (Exception ex) when (attemptCount > 0)
|
||||
{
|
||||
Trace.Info($"Catch exception during connect. {attemptCount} attempt left.");
|
||||
Trace.Error(ex);
|
||||
|
||||
await HostContext.Delay(TimeSpan.FromMilliseconds(100), CancellationToken.None);
|
||||
}
|
||||
}
|
||||
|
||||
// should never reach here.
|
||||
throw new InvalidOperationException(nameof(EstablishVssConnection));
|
||||
}
|
||||
|
||||
private void CheckConnection(RunnerConnectionType connectionType)
|
||||
{
|
||||
switch (connectionType)
|
||||
@@ -297,10 +272,10 @@ namespace GitHub.Runner.Common
|
||||
return _messageTaskAgentClient.DeleteAgentSessionAsync(poolId, sessionId, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task<TaskAgentMessage> GetAgentMessageAsync(Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, CancellationToken cancellationToken)
|
||||
public Task<TaskAgentMessage> GetAgentMessageAsync(Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, string runnerVersion, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckConnection(RunnerConnectionType.MessageQueue);
|
||||
return _messageTaskAgentClient.GetMessageAsync(poolId, sessionId, lastMessageId, status, cancellationToken: cancellationToken);
|
||||
return _messageTaskAgentClient.GetMessageAsync(poolId, sessionId, lastMessageId, status, runnerVersion, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.WebApi;
|
||||
using Sdk.WebApi.WebApi.RawClient;
|
||||
|
||||
namespace GitHub.Runner.Common
|
||||
{
|
||||
@@ -21,9 +27,9 @@ namespace GitHub.Runner.Common
|
||||
protected IHostContext HostContext { get; private set; }
|
||||
protected Tracing Trace { get; private set; }
|
||||
|
||||
public string TraceName
|
||||
public string TraceName
|
||||
{
|
||||
get
|
||||
get
|
||||
{
|
||||
return GetType().Name;
|
||||
}
|
||||
@@ -35,5 +41,70 @@ namespace GitHub.Runner.Common
|
||||
Trace = HostContext.GetTrace(TraceName);
|
||||
Trace.Entering();
|
||||
}
|
||||
|
||||
protected async Task<VssConnection> EstablishVssConnection(Uri serverUrl, VssCredentials credentials, TimeSpan timeout)
|
||||
{
|
||||
Trace.Info($"EstablishVssConnection");
|
||||
Trace.Info($"Establish connection with {timeout.TotalSeconds} seconds timeout.");
|
||||
int attemptCount = 5;
|
||||
while (attemptCount-- > 0)
|
||||
{
|
||||
var connection = VssUtil.CreateConnection(serverUrl, credentials, timeout: timeout);
|
||||
try
|
||||
{
|
||||
await connection.ConnectAsync();
|
||||
return connection;
|
||||
}
|
||||
catch (Exception ex) when (attemptCount > 0)
|
||||
{
|
||||
Trace.Info($"Catch exception during connect. {attemptCount} attempt left.");
|
||||
Trace.Error(ex);
|
||||
|
||||
await HostContext.Delay(TimeSpan.FromMilliseconds(100), CancellationToken.None);
|
||||
}
|
||||
}
|
||||
|
||||
// should never reach here.
|
||||
throw new InvalidOperationException(nameof(EstablishVssConnection));
|
||||
}
|
||||
|
||||
protected async Task RetryRequest(Func<Task> func,
|
||||
CancellationToken cancellationToken,
|
||||
int maxRetryAttemptsCount = 5
|
||||
)
|
||||
{
|
||||
async Task<Unit> wrappedFunc()
|
||||
{
|
||||
await func();
|
||||
return Unit.Value;
|
||||
}
|
||||
await RetryRequest<Unit>(wrappedFunc, cancellationToken, maxRetryAttemptsCount);
|
||||
}
|
||||
|
||||
protected async Task<T> RetryRequest<T>(Func<Task<T>> func,
|
||||
CancellationToken cancellationToken,
|
||||
int maxRetryAttemptsCount = 5
|
||||
)
|
||||
{
|
||||
var retryCount = 0;
|
||||
while (true)
|
||||
{
|
||||
retryCount++;
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
try
|
||||
{
|
||||
return await func();
|
||||
}
|
||||
// TODO: Add handling of non-retriable exceptions: https://github.com/github/actions-broker/issues/122
|
||||
catch (Exception ex) when (retryCount < maxRetryAttemptsCount)
|
||||
{
|
||||
Trace.Error("Catch exception during request");
|
||||
Trace.Error(ex);
|
||||
var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15));
|
||||
Trace.Warning($"Back off {backOff.TotalSeconds} seconds before next retry. {maxRetryAttemptsCount - retryCount} attempt left.");
|
||||
await Task.Delay(backOff, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
96
src/Runner.Common/StdoutTraceListener.cs
Normal file
96
src/Runner.Common/StdoutTraceListener.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using GitHub.Runner.Sdk;
|
||||
|
||||
namespace GitHub.Runner.Common
|
||||
{
|
||||
public sealed class StdoutTraceListener : ConsoleTraceListener
|
||||
{
|
||||
private readonly string _hostType;
|
||||
|
||||
public StdoutTraceListener(string hostType)
|
||||
{
|
||||
this._hostType = hostType;
|
||||
}
|
||||
|
||||
// Copied and modified slightly from .Net Core source code. Modification was required to make it compile.
|
||||
// There must be some TraceFilter extension class that is missing in this source code.
|
||||
public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message)
|
||||
{
|
||||
if (Filter != null && !Filter.ShouldTrace(eventCache, source, eventType, id, message, null, null, null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(message))
|
||||
{
|
||||
var messageLines = message.Split(Environment.NewLine);
|
||||
foreach (var messageLine in messageLines)
|
||||
{
|
||||
WriteHeader(source, eventType, id);
|
||||
WriteLine(messageLine);
|
||||
WriteFooter(eventCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsEnabled(TraceOptions opts)
|
||||
{
|
||||
return (opts & TraceOutputOptions) != 0;
|
||||
}
|
||||
|
||||
// Altered from the original .Net Core implementation.
|
||||
private void WriteHeader(string source, TraceEventType eventType, int id)
|
||||
{
|
||||
string type = null;
|
||||
switch (eventType)
|
||||
{
|
||||
case TraceEventType.Critical:
|
||||
type = "CRIT";
|
||||
break;
|
||||
case TraceEventType.Error:
|
||||
type = "ERR ";
|
||||
break;
|
||||
case TraceEventType.Warning:
|
||||
type = "WARN";
|
||||
break;
|
||||
case TraceEventType.Information:
|
||||
type = "INFO";
|
||||
break;
|
||||
case TraceEventType.Verbose:
|
||||
type = "VERB";
|
||||
break;
|
||||
default:
|
||||
type = eventType.ToString();
|
||||
break;
|
||||
}
|
||||
|
||||
Write(StringUtil.Format("[{0} {1:u} {2} {3}] ", _hostType.ToUpperInvariant(), DateTime.UtcNow, type, source));
|
||||
}
|
||||
|
||||
// Copied and modified slightly from .Net Core source code to make it compile. The original code
|
||||
// accesses a private indentLevel field. In this code it has been modified to use the getter/setter.
|
||||
private void WriteFooter(TraceEventCache eventCache)
|
||||
{
|
||||
if (eventCache == null)
|
||||
return;
|
||||
|
||||
IndentLevel++;
|
||||
if (IsEnabled(TraceOptions.ProcessId))
|
||||
WriteLine("ProcessId=" + eventCache.ProcessId);
|
||||
|
||||
if (IsEnabled(TraceOptions.ThreadId))
|
||||
WriteLine("ThreadId=" + eventCache.ThreadId);
|
||||
|
||||
if (IsEnabled(TraceOptions.DateTime))
|
||||
WriteLine("DateTime=" + eventCache.DateTime.ToString("o", CultureInfo.InvariantCulture));
|
||||
|
||||
if (IsEnabled(TraceOptions.Timestamp))
|
||||
WriteLine("Timestamp=" + eventCache.Timestamp);
|
||||
|
||||
IndentLevel--;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ namespace GitHub.Runner.Common
|
||||
string ReadSecret();
|
||||
void Write(string message, ConsoleColor? colorCode = null);
|
||||
void WriteLine();
|
||||
void WriteLine(string line, ConsoleColor? colorCode = null);
|
||||
void WriteLine(string line, ConsoleColor? colorCode = null, bool skipTracing = false);
|
||||
void WriteError(Exception ex);
|
||||
void WriteError(string line);
|
||||
void WriteSection(string message);
|
||||
@@ -81,7 +81,7 @@ namespace GitHub.Runner.Common
|
||||
}
|
||||
|
||||
// Trace whether a value was entered.
|
||||
string val = new String(chars.ToArray());
|
||||
string val = new(chars.ToArray());
|
||||
if (!string.IsNullOrEmpty(val))
|
||||
{
|
||||
HostContext.SecretMasker.AddValue(val);
|
||||
@@ -116,9 +116,12 @@ namespace GitHub.Runner.Common
|
||||
|
||||
// Do not add a format string overload. Terminal messages are user facing and therefore
|
||||
// should be localized. Use the Loc method in the StringUtil class.
|
||||
public void WriteLine(string line, ConsoleColor? colorCode = null)
|
||||
public void WriteLine(string line, ConsoleColor? colorCode = null, bool skipTracing = false)
|
||||
{
|
||||
Trace.Info($"WRITE LINE: {line}");
|
||||
if (!skipTracing)
|
||||
{
|
||||
Trace.Info($"WRITE LINE: {line}");
|
||||
}
|
||||
if (!Silent)
|
||||
{
|
||||
if (colorCode != null)
|
||||
|
||||
@@ -14,23 +14,25 @@ namespace GitHub.Runner.Common
|
||||
|
||||
public sealed class TraceManager : ITraceManager
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, Tracing> _sources = new ConcurrentDictionary<string, Tracing>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly ConcurrentDictionary<string, Tracing> _sources = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly HostTraceListener _hostTraceListener;
|
||||
private readonly StdoutTraceListener _stdoutTraceListener;
|
||||
private TraceSetting _traceSetting;
|
||||
private ISecretMasker _secretMasker;
|
||||
|
||||
public TraceManager(HostTraceListener traceListener, ISecretMasker secretMasker)
|
||||
: this(traceListener, new TraceSetting(), secretMasker)
|
||||
public TraceManager(HostTraceListener traceListener, StdoutTraceListener stdoutTraceListener, ISecretMasker secretMasker)
|
||||
: this(traceListener, stdoutTraceListener, new TraceSetting(), secretMasker)
|
||||
{
|
||||
}
|
||||
|
||||
public TraceManager(HostTraceListener traceListener, TraceSetting traceSetting, ISecretMasker secretMasker)
|
||||
public TraceManager(HostTraceListener traceListener, StdoutTraceListener stdoutTraceListener, TraceSetting traceSetting, ISecretMasker secretMasker)
|
||||
{
|
||||
// Validate and store params.
|
||||
ArgUtil.NotNull(traceListener, nameof(traceListener));
|
||||
ArgUtil.NotNull(traceSetting, nameof(traceSetting));
|
||||
ArgUtil.NotNull(secretMasker, nameof(secretMasker));
|
||||
_hostTraceListener = traceListener;
|
||||
_stdoutTraceListener = stdoutTraceListener;
|
||||
_traceSetting = traceSetting;
|
||||
_secretMasker = secretMasker;
|
||||
|
||||
@@ -81,7 +83,7 @@ namespace GitHub.Runner.Common
|
||||
Level = sourceTraceLevel.ToSourceLevels()
|
||||
};
|
||||
}
|
||||
return new Tracing(name, _secretMasker, sourceSwitch, _hostTraceListener);
|
||||
return new Tracing(name, _secretMasker, sourceSwitch, _hostTraceListener, _stdoutTraceListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace GitHub.Runner.Common
|
||||
private ISecretMasker _secretMasker;
|
||||
private TraceSource _traceSource;
|
||||
|
||||
public Tracing(string name, ISecretMasker secretMasker, SourceSwitch sourceSwitch, HostTraceListener traceListener)
|
||||
public Tracing(string name, ISecretMasker secretMasker, SourceSwitch sourceSwitch, HostTraceListener traceListener, StdoutTraceListener stdoutTraceListener = null)
|
||||
{
|
||||
ArgUtil.NotNull(secretMasker, nameof(secretMasker));
|
||||
_secretMasker = secretMasker;
|
||||
@@ -27,6 +27,10 @@ namespace GitHub.Runner.Common
|
||||
}
|
||||
|
||||
_traceSource.Listeners.Add(traceListener);
|
||||
if (stdoutTraceListener != null)
|
||||
{
|
||||
_traceSource.Listeners.Add(stdoutTraceListener);
|
||||
}
|
||||
}
|
||||
|
||||
public void Info(string message)
|
||||
|
||||
8
src/Runner.Common/Unit.cs
Normal file
8
src/Runner.Common/Unit.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
// Represents absence of value.
|
||||
namespace GitHub.Runner.Common
|
||||
{
|
||||
public readonly struct Unit
|
||||
{
|
||||
public static readonly Unit Value = default;
|
||||
}
|
||||
}
|
||||
14
src/Runner.Common/Util/MessageUtil.cs
Normal file
14
src/Runner.Common/Util/MessageUtil.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace GitHub.Runner.Common.Util
|
||||
{
|
||||
using System;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
|
||||
public static class MessageUtil
|
||||
{
|
||||
public static bool IsRunServiceJob(string messageType)
|
||||
{
|
||||
return string.Equals(messageType, JobRequestMessageTypes.RunnerJobRequest, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,8 +347,8 @@ namespace GitHub.Runner.Listener.Check
|
||||
public sealed class HttpEventSourceListener : EventListener
|
||||
{
|
||||
private readonly List<string> _logs;
|
||||
private readonly object _lock = new object();
|
||||
private readonly Dictionary<string, HashSet<string>> _ignoredEvent = new Dictionary<string, HashSet<string>>
|
||||
private readonly object _lock = new();
|
||||
private readonly Dictionary<string, HashSet<string>> _ignoredEvent = new()
|
||||
{
|
||||
{
|
||||
"Microsoft-System-Net-Http",
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace GitHub.Runner.Listener.Check
|
||||
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||
|
||||
// Request to github.com or ghes server
|
||||
Uri requestUrl = new Uri(url);
|
||||
Uri requestUrl = new(url);
|
||||
var env = new Dictionary<string, string>()
|
||||
{
|
||||
{ "HOSTNAME", requestUrl.Host },
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace GitHub.Runner.Listener
|
||||
{
|
||||
public sealed class CommandSettings
|
||||
{
|
||||
private readonly Dictionary<string, string> _envArgs = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, string> _envArgs = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly CommandLineParser _parser;
|
||||
private readonly IPromptManager _promptManager;
|
||||
private readonly Tracing _trace;
|
||||
@@ -26,7 +26,7 @@ namespace GitHub.Runner.Listener
|
||||
};
|
||||
|
||||
// Valid flags and args for specific command - key: command, value: array of valid flags and args
|
||||
private readonly Dictionary<string, string[]> validOptions = new Dictionary<string, string[]>
|
||||
private readonly Dictionary<string, string[]> validOptions = new()
|
||||
{
|
||||
// Valid configure flags and args
|
||||
[Constants.Runner.CommandLine.Commands.Configure] =
|
||||
@@ -34,6 +34,7 @@ namespace GitHub.Runner.Listener
|
||||
{
|
||||
Constants.Runner.CommandLine.Flags.DisableUpdate,
|
||||
Constants.Runner.CommandLine.Flags.Ephemeral,
|
||||
Constants.Runner.CommandLine.Flags.GenerateServiceConfig,
|
||||
Constants.Runner.CommandLine.Flags.Replace,
|
||||
Constants.Runner.CommandLine.Flags.RunAsService,
|
||||
Constants.Runner.CommandLine.Flags.Unattended,
|
||||
@@ -55,7 +56,8 @@ namespace GitHub.Runner.Listener
|
||||
new string[]
|
||||
{
|
||||
Constants.Runner.CommandLine.Args.Token,
|
||||
Constants.Runner.CommandLine.Args.PAT
|
||||
Constants.Runner.CommandLine.Args.PAT,
|
||||
Constants.Runner.CommandLine.Flags.Local
|
||||
},
|
||||
// Valid run flags and args
|
||||
[Constants.Runner.CommandLine.Commands.Run] =
|
||||
@@ -79,11 +81,13 @@ namespace GitHub.Runner.Listener
|
||||
// Flags.
|
||||
public bool Check => TestFlag(Constants.Runner.CommandLine.Flags.Check);
|
||||
public bool Commit => TestFlag(Constants.Runner.CommandLine.Flags.Commit);
|
||||
public bool DisableUpdate => TestFlag(Constants.Runner.CommandLine.Flags.DisableUpdate);
|
||||
public bool Ephemeral => TestFlag(Constants.Runner.CommandLine.Flags.Ephemeral);
|
||||
public bool GenerateServiceConfig => TestFlag(Constants.Runner.CommandLine.Flags.GenerateServiceConfig);
|
||||
public bool Help => TestFlag(Constants.Runner.CommandLine.Flags.Help);
|
||||
public bool Unattended => TestFlag(Constants.Runner.CommandLine.Flags.Unattended);
|
||||
public bool Version => TestFlag(Constants.Runner.CommandLine.Flags.Version);
|
||||
public bool Ephemeral => TestFlag(Constants.Runner.CommandLine.Flags.Ephemeral);
|
||||
public bool DisableUpdate => TestFlag(Constants.Runner.CommandLine.Flags.DisableUpdate);
|
||||
public bool RemoveLocalConfig => TestFlag(Constants.Runner.CommandLine.Flags.Local);
|
||||
|
||||
// Keep this around since customers still relies on it
|
||||
public bool RunOnce => TestFlag(Constants.Runner.CommandLine.Flags.Once);
|
||||
@@ -137,7 +141,7 @@ namespace GitHub.Runner.Listener
|
||||
// Validate commandline parser result
|
||||
public List<string> Validate()
|
||||
{
|
||||
List<string> unknowns = new List<string>();
|
||||
List<string> unknowns = new();
|
||||
|
||||
// detect unknown commands
|
||||
unknowns.AddRange(_parser.Commands.Where(x => !validOptions.Keys.Contains(x, StringComparer.OrdinalIgnoreCase)));
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.Common.Internal;
|
||||
using GitHub.Services.OAuth;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -14,6 +7,13 @@ using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.Common.Internal;
|
||||
using GitHub.Services.OAuth;
|
||||
|
||||
namespace GitHub.Runner.Listener.Configuration
|
||||
{
|
||||
@@ -81,12 +81,33 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
_term.WriteLine("--------------------------------------------------------------------------------");
|
||||
|
||||
Trace.Info(nameof(ConfigureAsync));
|
||||
|
||||
if (command.GenerateServiceConfig)
|
||||
{
|
||||
#if OS_LINUX
|
||||
if (!IsConfigured())
|
||||
{
|
||||
throw new InvalidOperationException("--generateServiceConfig requires that the runner is already configured. For configuring a new runner as a service, run './config.sh'.");
|
||||
}
|
||||
|
||||
RunnerSettings settings = _store.GetSettings();
|
||||
|
||||
Trace.Info($"generate service config for runner: {settings.AgentId}");
|
||||
var controlManager = HostContext.GetService<ILinuxServiceControlManager>();
|
||||
controlManager.GenerateScripts(settings);
|
||||
|
||||
return;
|
||||
#else
|
||||
throw new NotSupportedException("--generateServiceConfig is only supported on Linux.");
|
||||
#endif
|
||||
}
|
||||
|
||||
if (IsConfigured())
|
||||
{
|
||||
throw new InvalidOperationException("Cannot configure the runner because it is already configured. To reconfigure the runner, run 'config.cmd remove' or './config.sh remove' first.");
|
||||
}
|
||||
|
||||
RunnerSettings runnerSettings = new RunnerSettings();
|
||||
RunnerSettings runnerSettings = new();
|
||||
|
||||
// Loop getting url and creds until you can connect
|
||||
ICredentialProvider credProvider = null;
|
||||
@@ -521,7 +542,7 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
|
||||
private TaskAgent CreateNewAgent(string agentName, RSAParameters publicKey, ISet<string> userLabels, bool ephemeral, bool disableUpdate)
|
||||
{
|
||||
TaskAgent agent = new TaskAgent(agentName)
|
||||
TaskAgent agent = new(agentName)
|
||||
{
|
||||
Authorization = new TaskAgentAuthorization
|
||||
{
|
||||
@@ -615,7 +636,7 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
}
|
||||
|
||||
int retryCount = 0;
|
||||
while(retryCount < 3)
|
||||
while (retryCount < 3)
|
||||
{
|
||||
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
||||
using (var httpClient = new HttpClient(httpClientHandler))
|
||||
@@ -625,28 +646,29 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("basic", base64EncodingToken);
|
||||
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
||||
httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/vnd.github.v3+json");
|
||||
|
||||
|
||||
var responseStatus = System.Net.HttpStatusCode.OK;
|
||||
try
|
||||
{
|
||||
var response = await httpClient.PostAsync(githubApiUrl, new StringContent(string.Empty));
|
||||
responseStatus = response.StatusCode;
|
||||
var githubRequestId = GetGitHubRequestId(response.Headers);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
Trace.Info($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'");
|
||||
Trace.Info($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}' ({githubRequestId})");
|
||||
var jsonResponse = await response.Content.ReadAsStringAsync();
|
||||
return StringUtil.ConvertFromJson<GitHubRunnerRegisterToken>(jsonResponse);
|
||||
}
|
||||
else
|
||||
{
|
||||
_term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'");
|
||||
_term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}' (Request Id: {githubRequestId})");
|
||||
var errorResponse = await response.Content.ReadAsStringAsync();
|
||||
_term.WriteError(errorResponse);
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
}
|
||||
catch(Exception ex) when (retryCount < 2 && responseStatus != System.Net.HttpStatusCode.NotFound)
|
||||
catch (Exception ex) when (retryCount < 2 && responseStatus != System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
retryCount++;
|
||||
Trace.Error($"Failed to get JIT runner token -- Atempt: {retryCount}");
|
||||
@@ -693,22 +715,23 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
{
|
||||
var response = await httpClient.PostAsync(githubApiUrl, new StringContent(StringUtil.ConvertToJson(bodyObject), null, "application/json"));
|
||||
responseStatus = response.StatusCode;
|
||||
var githubRequestId = GetGitHubRequestId(response.Headers);
|
||||
|
||||
if(response.IsSuccessStatusCode)
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
Trace.Info($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'");
|
||||
Trace.Info($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}' ({githubRequestId})");
|
||||
var jsonResponse = await response.Content.ReadAsStringAsync();
|
||||
return StringUtil.ConvertFromJson<GitHubAuthResult>(jsonResponse);
|
||||
}
|
||||
else
|
||||
{
|
||||
_term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'");
|
||||
_term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}' (Request Id: {githubRequestId})");
|
||||
var errorResponse = await response.Content.ReadAsStringAsync();
|
||||
_term.WriteError(errorResponse);
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
}
|
||||
catch(Exception ex) when (retryCount < 2 && responseStatus != System.Net.HttpStatusCode.NotFound)
|
||||
catch (Exception ex) when (retryCount < 2 && responseStatus != System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
retryCount++;
|
||||
Trace.Error($"Failed to get tenant credentials -- Atempt: {retryCount}");
|
||||
@@ -721,5 +744,14 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private string GetGitHubRequestId(HttpResponseHeaders headers)
|
||||
{
|
||||
if (headers.TryGetValues("x-github-request-id", out var headerValues))
|
||||
{
|
||||
return headerValues.FirstOrDefault();
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
|
||||
public class CredentialManager : RunnerService, ICredentialManager
|
||||
{
|
||||
public static readonly Dictionary<string, Type> CredentialTypes = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
|
||||
public static readonly Dictionary<string, Type> CredentialTypes = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ Constants.Configuration.OAuth, typeof(OAuthCredential)},
|
||||
{ Constants.Configuration.OAuthAccessToken, typeof(OAuthAccessTokenCredential)},
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
ArgUtil.NotNullOrEmpty(token, nameof(token));
|
||||
|
||||
trace.Info("token retrieved: {0} chars", token.Length);
|
||||
VssCredentials creds = new VssCredentials(new VssOAuthAccessTokenCredential(token), CredentialPromptType.DoNotPrompt);
|
||||
VssCredentials creds = new(new VssOAuthAccessTokenCredential(token), CredentialPromptType.DoNotPrompt);
|
||||
trace.Info("cred created");
|
||||
|
||||
return creds;
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
}
|
||||
|
||||
// For the service name, replace any characters outside of the alpha-numeric set and ".", "_", "-" with "-"
|
||||
Regex regex = new Regex(@"[^0-9a-zA-Z._\-]");
|
||||
Regex regex = new(@"[^0-9a-zA-Z._\-]");
|
||||
string repoOrOrgName = regex.Replace(settings.RepoOrOrgName, "-");
|
||||
|
||||
serviceName = StringUtil.Format(serviceNamePattern, repoOrOrgName, settings.AgentName);
|
||||
|
||||
3
src/Runner.Listener/InternalsVisibleTo.cs
Normal file
3
src/Runner.Listener/InternalsVisibleTo.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Test")]
|
||||
@@ -7,6 +7,7 @@ using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.Pipelines;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Common.Util;
|
||||
@@ -37,8 +38,8 @@ namespace GitHub.Runner.Listener
|
||||
// and the server will not send another job while this one is still running.
|
||||
public sealed class JobDispatcher : RunnerService, IJobDispatcher
|
||||
{
|
||||
private static Regex _invalidJsonRegex = new Regex(@"invalid\ Json\ at\ position\ '(\d+)':", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private readonly Lazy<Dictionary<long, TaskResult>> _localRunJobResult = new Lazy<Dictionary<long, TaskResult>>();
|
||||
private static Regex _invalidJsonRegex = new(@"invalid\ Json\ at\ position\ '(\d+)':", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private readonly Lazy<Dictionary<long, TaskResult>> _localRunJobResult = new();
|
||||
private int _poolId;
|
||||
|
||||
IConfigurationStore _configurationStore;
|
||||
@@ -47,17 +48,19 @@ namespace GitHub.Runner.Listener
|
||||
private static readonly string _workerProcessName = $"Runner.Worker{IOUtil.ExeExtension}";
|
||||
|
||||
// this is not thread-safe
|
||||
private readonly Queue<Guid> _jobDispatchedQueue = new Queue<Guid>();
|
||||
private readonly ConcurrentDictionary<Guid, WorkerDispatcher> _jobInfos = new ConcurrentDictionary<Guid, WorkerDispatcher>();
|
||||
private readonly Queue<Guid> _jobDispatchedQueue = new();
|
||||
private readonly ConcurrentDictionary<Guid, WorkerDispatcher> _jobInfos = new();
|
||||
|
||||
// allow up to 30sec for any data to be transmitted over the process channel
|
||||
// timeout limit can be overwritten by environment GITHUB_ACTIONS_RUNNER_CHANNEL_TIMEOUT
|
||||
private TimeSpan _channelTimeout;
|
||||
|
||||
private TaskCompletionSource<bool> _runOnceJobCompleted = new TaskCompletionSource<bool>();
|
||||
private TaskCompletionSource<bool> _runOnceJobCompleted = new();
|
||||
|
||||
public event EventHandler<JobStatusEventArgs> JobStatus;
|
||||
|
||||
private bool _isRunServiceJob;
|
||||
|
||||
public override void Initialize(IHostContext hostContext)
|
||||
{
|
||||
base.Initialize(hostContext);
|
||||
@@ -86,6 +89,8 @@ namespace GitHub.Runner.Listener
|
||||
{
|
||||
Trace.Info($"Job request {jobRequestMessage.RequestId} for plan {jobRequestMessage.Plan.PlanId} job {jobRequestMessage.JobId} received.");
|
||||
|
||||
_isRunServiceJob = MessageUtil.IsRunServiceJob(jobRequestMessage.MessageType);
|
||||
|
||||
WorkerDispatcher currentDispatch = null;
|
||||
if (_jobDispatchedQueue.Count > 0)
|
||||
{
|
||||
@@ -111,7 +116,7 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
}
|
||||
|
||||
WorkerDispatcher newDispatch = new WorkerDispatcher(jobRequestMessage.JobId, jobRequestMessage.RequestId);
|
||||
WorkerDispatcher newDispatch = new(jobRequestMessage.JobId, jobRequestMessage.RequestId);
|
||||
if (runOnce)
|
||||
{
|
||||
Trace.Info("Start dispatcher for one time used runner.");
|
||||
@@ -239,6 +244,13 @@ namespace GitHub.Runner.Listener
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._isRunServiceJob)
|
||||
{
|
||||
Trace.Error($"We are not yet checking the state of jobrequest {jobDispatch.JobId} status. Cancel running worker right away.");
|
||||
jobDispatch.WorkerCancellationTokenSource.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
// based on the current design, server will only send one job for a given runner at a time.
|
||||
// if the runner received a new job request while a previous job request is still running, this typically indicates two situations
|
||||
// 1. a runner bug caused a server and runner mismatch on the state of the job request, e.g. the runner didn't renew the jobrequest
|
||||
@@ -357,7 +369,7 @@ namespace GitHub.Runner.Listener
|
||||
term.WriteLine($"{DateTime.UtcNow:u}: Running job: {message.JobDisplayName}");
|
||||
|
||||
// first job request renew succeed.
|
||||
TaskCompletionSource<int> firstJobRequestRenewed = new TaskCompletionSource<int>();
|
||||
TaskCompletionSource<int> firstJobRequestRenewed = new();
|
||||
var notification = HostContext.GetService<IJobNotification>();
|
||||
|
||||
// lock renew cancellation token.
|
||||
@@ -367,9 +379,11 @@ namespace GitHub.Runner.Listener
|
||||
long requestId = message.RequestId;
|
||||
Guid lockToken = Guid.Empty; // lockToken has never been used, keep this here of compat
|
||||
|
||||
var systemConnection = message.Resources.Endpoints.SingleOrDefault(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// start renew job request
|
||||
Trace.Info($"Start renew job request {requestId} for job {message.JobId}.");
|
||||
Task renewJobRequest = RenewJobRequestAsync(_poolId, requestId, lockToken, orchestrationId, firstJobRequestRenewed, lockRenewalTokenSource.Token);
|
||||
Task renewJobRequest = RenewJobRequestAsync(message, systemConnection, _poolId, requestId, lockToken, orchestrationId, firstJobRequestRenewed, lockRenewalTokenSource.Token);
|
||||
|
||||
// wait till first renew succeed or job request is cancelled
|
||||
// not even start worker if the first renew fail
|
||||
@@ -398,8 +412,9 @@ namespace GitHub.Runner.Listener
|
||||
HostContext.WritePerfCounter($"JobRequestRenewed_{requestId.ToString()}");
|
||||
|
||||
Task<int> workerProcessTask = null;
|
||||
object _outputLock = new object();
|
||||
List<string> workerOutput = new List<string>();
|
||||
object _outputLock = new();
|
||||
List<string> workerOutput = new();
|
||||
bool printToStdout = StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable(Constants.Variables.Agent.PrintLogToStdout));
|
||||
using (var processChannel = HostContext.CreateService<IProcessChannel>())
|
||||
using (var processInvoker = HostContext.CreateService<IProcessInvoker>())
|
||||
{
|
||||
@@ -421,7 +436,15 @@ namespace GitHub.Runner.Listener
|
||||
{
|
||||
lock (_outputLock)
|
||||
{
|
||||
workerOutput.Add(stdout.Data);
|
||||
if (!stdout.Data.StartsWith("[WORKER"))
|
||||
{
|
||||
workerOutput.Add(stdout.Data);
|
||||
}
|
||||
|
||||
if (printToStdout)
|
||||
{
|
||||
term.WriteLine(stdout.Data, skipTracing: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -499,7 +522,6 @@ namespace GitHub.Runner.Listener
|
||||
|
||||
// we get first jobrequest renew succeed and start the worker process with the job message.
|
||||
// send notification to machine provisioner.
|
||||
var systemConnection = message.Resources.Endpoints.SingleOrDefault(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
||||
var accessToken = systemConnection?.Authorization?.Parameters["AccessToken"];
|
||||
notification.JobStarted(message.JobId, accessToken, systemConnection.Url);
|
||||
|
||||
@@ -522,11 +544,8 @@ namespace GitHub.Runner.Listener
|
||||
detailInfo = string.Join(Environment.NewLine, workerOutput);
|
||||
Trace.Info($"Return code {returnCode} indicate worker encounter an unhandled exception or app crash, attach worker stdout/stderr to JobRequest result.");
|
||||
|
||||
var jobServer = HostContext.GetService<IJobServer>();
|
||||
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
|
||||
VssConnection jobConnection = VssUtil.CreateConnection(systemConnection.Url, jobServerCredential);
|
||||
await jobServer.ConnectAsync(jobConnection);
|
||||
|
||||
var jobServer = await InitializeJobServerAsync(systemConnection);
|
||||
await LogWorkerProcessUnhandledException(jobServer, message, detailInfo);
|
||||
|
||||
// Go ahead to finish the job with result 'Failed' if the STDERR from worker is System.IO.IOException, since it typically means we are running out of disk space.
|
||||
@@ -658,7 +677,7 @@ namespace GitHub.Runner.Listener
|
||||
finally
|
||||
{
|
||||
Busy = false;
|
||||
|
||||
|
||||
if (JobStatus != null)
|
||||
{
|
||||
JobStatus(this, new JobStatusEventArgs(TaskAgentStatus.Online));
|
||||
@@ -666,9 +685,128 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RenewJobRequestAsync(int poolId, long requestId, Guid lockToken, string orchestrationId, TaskCompletionSource<int> firstJobRequestRenewed, CancellationToken token)
|
||||
internal async Task RenewJobRequestAsync(Pipelines.AgentJobRequestMessage message, ServiceEndpoint systemConnection, int poolId, long requestId, Guid lockToken, string orchestrationId, TaskCompletionSource<int> firstJobRequestRenewed, CancellationToken token)
|
||||
{
|
||||
if (this._isRunServiceJob)
|
||||
{
|
||||
var runServer = await GetRunServerAsync(systemConnection);
|
||||
await RenewJobRequestAsync(runServer, message.Plan.PlanId, message.JobId, firstJobRequestRenewed, token);
|
||||
}
|
||||
else
|
||||
{
|
||||
var runnerServer = HostContext.GetService<IRunnerServer>();
|
||||
await RenewJobRequestAsync(runnerServer, poolId, requestId, lockToken, orchestrationId, firstJobRequestRenewed, token);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RenewJobRequestAsync(IRunServer runServer, Guid planId, Guid jobId, TaskCompletionSource<int> firstJobRequestRenewed, CancellationToken token)
|
||||
{
|
||||
TaskAgentJobRequest request = null;
|
||||
int firstRenewRetryLimit = 5;
|
||||
int encounteringError = 0;
|
||||
|
||||
// renew lock during job running.
|
||||
// stop renew only if cancellation token for lock renew task been signal or exception still happen after retry.
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var renewResponse = await runServer.RenewJobAsync(planId, jobId, token);
|
||||
Trace.Info($"Successfully renew job {jobId}, job is valid till {renewResponse.LockedUntil}");
|
||||
|
||||
if (!firstJobRequestRenewed.Task.IsCompleted)
|
||||
{
|
||||
// fire first renew succeed event.
|
||||
firstJobRequestRenewed.TrySetResult(0);
|
||||
}
|
||||
|
||||
if (encounteringError > 0)
|
||||
{
|
||||
encounteringError = 0;
|
||||
HostContext.WritePerfCounter("JobRenewRecovered");
|
||||
}
|
||||
|
||||
// renew again after 60 sec delay
|
||||
await HostContext.Delay(TimeSpan.FromSeconds(60), token);
|
||||
}
|
||||
catch (TaskOrchestrationJobNotFoundException)
|
||||
{
|
||||
// no need for retry. the job is not valid anymore.
|
||||
Trace.Info($"TaskAgentJobNotFoundException received when renew job {jobId}, job is no longer valid, stop renew job request.");
|
||||
return;
|
||||
}
|
||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||
{
|
||||
// OperationCanceledException may caused by http timeout or _lockRenewalTokenSource.Cance();
|
||||
// Stop renew only on cancellation token fired.
|
||||
Trace.Info($"job renew has been cancelled, stop renew job {jobId}.");
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error($"Catch exception during renew runner job {jobId}.");
|
||||
Trace.Error(ex);
|
||||
encounteringError++;
|
||||
|
||||
// retry
|
||||
TimeSpan remainingTime = TimeSpan.Zero;
|
||||
if (!firstJobRequestRenewed.Task.IsCompleted)
|
||||
{
|
||||
// retry 5 times every 10 sec for the first renew
|
||||
if (firstRenewRetryLimit-- > 0)
|
||||
{
|
||||
remainingTime = TimeSpan.FromSeconds(10);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// retry till reach lockeduntil + 5 mins extra buffer.
|
||||
remainingTime = request.LockedUntil.Value + TimeSpan.FromMinutes(5) - DateTime.UtcNow;
|
||||
}
|
||||
|
||||
if (remainingTime > TimeSpan.Zero)
|
||||
{
|
||||
TimeSpan delayTime;
|
||||
if (!firstJobRequestRenewed.Task.IsCompleted)
|
||||
{
|
||||
Trace.Info($"Retrying lock renewal for job {jobId}. The first job renew request has failed.");
|
||||
delayTime = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(10));
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace.Info($"Retrying lock renewal for job {jobId}. Job is valid until {request.LockedUntil.Value}.");
|
||||
if (encounteringError > 5)
|
||||
{
|
||||
delayTime = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(30));
|
||||
}
|
||||
else
|
||||
{
|
||||
delayTime = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15));
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// back-off before next retry.
|
||||
await HostContext.Delay(delayTime, token);
|
||||
}
|
||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||
{
|
||||
Trace.Info($"job renew has been cancelled, stop renew job {jobId}.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace.Info($"Lock renewal has run out of retry, stop renew lock for job {jobId}.");
|
||||
HostContext.WritePerfCounter("JobRenewReachLimit");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RenewJobRequestAsync(IRunnerServer runnerServer, int poolId, long requestId, Guid lockToken, string orchestrationId, TaskCompletionSource<int> firstJobRequestRenewed, CancellationToken token)
|
||||
{
|
||||
var runnerServer = HostContext.GetService<IRunnerServer>();
|
||||
TaskAgentJobRequest request = null;
|
||||
int firstRenewRetryLimit = 5;
|
||||
int encounteringError = 0;
|
||||
@@ -831,90 +969,93 @@ namespace GitHub.Runner.Listener
|
||||
var systemConnection = message.Resources.Endpoints.SingleOrDefault(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection));
|
||||
ArgUtil.NotNull(systemConnection, nameof(systemConnection));
|
||||
|
||||
var jobServer = HostContext.GetService<IJobServer>();
|
||||
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
|
||||
VssConnection jobConnection = VssUtil.CreateConnection(systemConnection.Url, jobServerCredential);
|
||||
var server = await InitializeJobServerAsync(systemConnection);
|
||||
|
||||
await jobServer.ConnectAsync(jobConnection);
|
||||
|
||||
var timeline = await jobServer.GetTimelineAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, CancellationToken.None);
|
||||
|
||||
var updatedRecords = new List<TimelineRecord>();
|
||||
var logPages = new Dictionary<Guid, Dictionary<int, string>>();
|
||||
var logRecords = new Dictionary<Guid, TimelineRecord>();
|
||||
foreach (var log in logs)
|
||||
if (server is IJobServer jobServer)
|
||||
{
|
||||
var logName = Path.GetFileNameWithoutExtension(log);
|
||||
var logNameParts = logName.Split('_', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (logNameParts.Length != 3)
|
||||
{
|
||||
Trace.Warning($"log file '{log}' doesn't follow naming convension 'GUID_GUID_INT'.");
|
||||
continue;
|
||||
}
|
||||
var logPageSeperator = logName.IndexOf('_');
|
||||
var logRecordId = Guid.Empty;
|
||||
var pageNumber = 0;
|
||||
var timeline = await jobServer.GetTimelineAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, CancellationToken.None);
|
||||
|
||||
if (!Guid.TryParse(logNameParts[0], out Guid timelineId) || timelineId != timeline.Id)
|
||||
var updatedRecords = new List<TimelineRecord>();
|
||||
var logPages = new Dictionary<Guid, Dictionary<int, string>>();
|
||||
var logRecords = new Dictionary<Guid, TimelineRecord>();
|
||||
foreach (var log in logs)
|
||||
{
|
||||
Trace.Warning($"log file '{log}' is not belongs to current job");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Guid.TryParse(logNameParts[1], out logRecordId))
|
||||
{
|
||||
Trace.Warning($"log file '{log}' doesn't follow naming convension 'GUID_GUID_INT'.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!int.TryParse(logNameParts[2], out pageNumber))
|
||||
{
|
||||
Trace.Warning($"log file '{log}' doesn't follow naming convension 'GUID_GUID_INT'.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var record = timeline.Records.FirstOrDefault(x => x.Id == logRecordId);
|
||||
if (record != null)
|
||||
{
|
||||
if (!logPages.ContainsKey(record.Id))
|
||||
var logName = Path.GetFileNameWithoutExtension(log);
|
||||
var logNameParts = logName.Split('_', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (logNameParts.Length != 3)
|
||||
{
|
||||
logPages[record.Id] = new Dictionary<int, string>();
|
||||
logRecords[record.Id] = record;
|
||||
Trace.Warning($"log file '{log}' doesn't follow naming convension 'GUID_GUID_INT'.");
|
||||
continue;
|
||||
}
|
||||
var logPageSeperator = logName.IndexOf('_');
|
||||
var logRecordId = Guid.Empty;
|
||||
var pageNumber = 0;
|
||||
|
||||
if (!Guid.TryParse(logNameParts[0], out Guid timelineId) || timelineId != timeline.Id)
|
||||
{
|
||||
Trace.Warning($"log file '{log}' is not belongs to current job");
|
||||
continue;
|
||||
}
|
||||
|
||||
logPages[record.Id][pageNumber] = log;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var pages in logPages)
|
||||
{
|
||||
var record = logRecords[pages.Key];
|
||||
if (record.Log == null)
|
||||
{
|
||||
// Create the log
|
||||
record.Log = await jobServer.CreateLogAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, new TaskLog(String.Format(@"logs\{0:D}", record.Id)), default(CancellationToken));
|
||||
|
||||
// Need to post timeline record updates to reflect the log creation
|
||||
updatedRecords.Add(record.Clone());
|
||||
}
|
||||
|
||||
for (var i = 1; i <= pages.Value.Count; i++)
|
||||
{
|
||||
var logFile = pages.Value[i];
|
||||
// Upload the contents
|
||||
using (FileStream fs = File.Open(logFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
if (!Guid.TryParse(logNameParts[1], out logRecordId))
|
||||
{
|
||||
var logUploaded = await jobServer.AppendLogContentAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, record.Log.Id, fs, default(CancellationToken));
|
||||
Trace.Warning($"log file '{log}' doesn't follow naming convension 'GUID_GUID_INT'.");
|
||||
continue;
|
||||
}
|
||||
|
||||
Trace.Info($"Uploaded unfinished log '{logFile}' for current job.");
|
||||
IOUtil.DeleteFile(logFile);
|
||||
if (!int.TryParse(logNameParts[2], out pageNumber))
|
||||
{
|
||||
Trace.Warning($"log file '{log}' doesn't follow naming convension 'GUID_GUID_INT'.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var record = timeline.Records.FirstOrDefault(x => x.Id == logRecordId);
|
||||
if (record != null)
|
||||
{
|
||||
if (!logPages.ContainsKey(record.Id))
|
||||
{
|
||||
logPages[record.Id] = new Dictionary<int, string>();
|
||||
logRecords[record.Id] = record;
|
||||
}
|
||||
|
||||
logPages[record.Id][pageNumber] = log;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var pages in logPages)
|
||||
{
|
||||
var record = logRecords[pages.Key];
|
||||
if (record.Log == null)
|
||||
{
|
||||
// Create the log
|
||||
record.Log = await jobServer.CreateLogAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, new TaskLog(String.Format(@"logs\{0:D}", record.Id)), default(CancellationToken));
|
||||
|
||||
// Need to post timeline record updates to reflect the log creation
|
||||
updatedRecords.Add(record.Clone());
|
||||
}
|
||||
|
||||
for (var i = 1; i <= pages.Value.Count; i++)
|
||||
{
|
||||
var logFile = pages.Value[i];
|
||||
// Upload the contents
|
||||
using (FileStream fs = File.Open(logFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
{
|
||||
var logUploaded = await jobServer.AppendLogContentAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, record.Log.Id, fs, default(CancellationToken));
|
||||
}
|
||||
|
||||
Trace.Info($"Uploaded unfinished log '{logFile}' for current job.");
|
||||
IOUtil.DeleteFile(logFile);
|
||||
}
|
||||
}
|
||||
|
||||
if (updatedRecords.Count > 0)
|
||||
{
|
||||
await jobServer.UpdateTimelineRecordsAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, updatedRecords, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
|
||||
if (updatedRecords.Count > 0)
|
||||
else
|
||||
{
|
||||
await jobServer.UpdateTimelineRecordsAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, updatedRecords, CancellationToken.None);
|
||||
Trace.Info("Job server does not support log upload yet.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -934,9 +1075,15 @@ namespace GitHub.Runner.Listener
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._isRunServiceJob)
|
||||
{
|
||||
Trace.Verbose($"Skip FinishAgentRequest call from Listener because MessageType is {message.MessageType}");
|
||||
return;
|
||||
}
|
||||
|
||||
var runnerServer = HostContext.GetService<IRunnerServer>();
|
||||
int completeJobRequestRetryLimit = 5;
|
||||
List<Exception> exceptions = new List<Exception>();
|
||||
List<Exception> exceptions = new();
|
||||
while (completeJobRequestRetryLimit-- > 0)
|
||||
{
|
||||
try
|
||||
@@ -970,66 +1117,117 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
|
||||
// log an error issue to job level timeline record
|
||||
private async Task LogWorkerProcessUnhandledException(IJobServer jobServer, Pipelines.AgentJobRequestMessage message, string errorMessage)
|
||||
private async Task LogWorkerProcessUnhandledException(IRunnerService server, Pipelines.AgentJobRequestMessage message, string errorMessage)
|
||||
{
|
||||
try
|
||||
if (server is IJobServer jobServer)
|
||||
{
|
||||
var timeline = await jobServer.GetTimelineAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, CancellationToken.None);
|
||||
ArgUtil.NotNull(timeline, nameof(timeline));
|
||||
|
||||
TimelineRecord jobRecord = timeline.Records.FirstOrDefault(x => x.Id == message.JobId && x.RecordType == "Job");
|
||||
ArgUtil.NotNull(jobRecord, nameof(jobRecord));
|
||||
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(errorMessage) &&
|
||||
message.Variables.TryGetValue("DistributedTask.EnableRunnerIPCDebug", out var enableRunnerIPCDebug) &&
|
||||
StringUtil.ConvertToBoolean(enableRunnerIPCDebug.Value))
|
||||
var timeline = await jobServer.GetTimelineAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, CancellationToken.None);
|
||||
ArgUtil.NotNull(timeline, nameof(timeline));
|
||||
|
||||
TimelineRecord jobRecord = timeline.Records.FirstOrDefault(x => x.Id == message.JobId && x.RecordType == "Job");
|
||||
ArgUtil.NotNull(jobRecord, nameof(jobRecord));
|
||||
|
||||
try
|
||||
{
|
||||
// the trace should be best effort and not affect any job result
|
||||
var match = _invalidJsonRegex.Match(errorMessage);
|
||||
if (match.Success &&
|
||||
match.Groups.Count == 2)
|
||||
if (!string.IsNullOrEmpty(errorMessage) &&
|
||||
message.Variables.TryGetValue("DistributedTask.EnableRunnerIPCDebug", out var enableRunnerIPCDebug) &&
|
||||
StringUtil.ConvertToBoolean(enableRunnerIPCDebug.Value))
|
||||
{
|
||||
var jsonPosition = int.Parse(match.Groups[1].Value);
|
||||
var serializedJobMessage = JsonUtility.ToString(message);
|
||||
var originalJson = serializedJobMessage.Substring(jsonPosition - 10, 20);
|
||||
errorMessage = $"Runner sent Json at position '{jsonPosition}': {originalJson} ({Convert.ToBase64String(Encoding.UTF8.GetBytes(originalJson))})\n{errorMessage}";
|
||||
// the trace should be best effort and not affect any job result
|
||||
var match = _invalidJsonRegex.Match(errorMessage);
|
||||
if (match.Success &&
|
||||
match.Groups.Count == 2)
|
||||
{
|
||||
var jsonPosition = int.Parse(match.Groups[1].Value);
|
||||
var serializedJobMessage = JsonUtility.ToString(message);
|
||||
var originalJson = serializedJobMessage.Substring(jsonPosition - 10, 20);
|
||||
errorMessage = $"Runner sent Json at position '{jsonPosition}': {originalJson} ({Convert.ToBase64String(Encoding.UTF8.GetBytes(originalJson))})\n{errorMessage}";
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error(ex);
|
||||
errorMessage = $"Fail to check json IPC error: {ex.Message}\n{errorMessage}";
|
||||
}
|
||||
|
||||
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = errorMessage };
|
||||
unhandledExceptionIssue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.WorkerCrash;
|
||||
jobRecord.ErrorCount++;
|
||||
jobRecord.Issues.Add(unhandledExceptionIssue);
|
||||
await jobServer.UpdateTimelineRecordsAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, new TimelineRecord[] { jobRecord }, CancellationToken.None);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error("Fail to report unhandled exception from Runner.Worker process");
|
||||
Trace.Error(ex);
|
||||
errorMessage = $"Fail to check json IPC error: {ex.Message}\n{errorMessage}";
|
||||
}
|
||||
|
||||
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = errorMessage };
|
||||
unhandledExceptionIssue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.WorkerCrash;
|
||||
jobRecord.ErrorCount++;
|
||||
jobRecord.Issues.Add(unhandledExceptionIssue);
|
||||
await jobServer.UpdateTimelineRecordsAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, new TimelineRecord[] { jobRecord }, CancellationToken.None);
|
||||
}
|
||||
catch (Exception ex)
|
||||
else
|
||||
{
|
||||
Trace.Error("Fail to report unhandled exception from Runner.Worker process");
|
||||
Trace.Error(ex);
|
||||
Trace.Info("Job server does not support handling unhandled exception yet, error message: {0}", errorMessage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// raise job completed event to fail the job.
|
||||
private async Task ForceFailJob(IJobServer jobServer, Pipelines.AgentJobRequestMessage message)
|
||||
private async Task ForceFailJob(IRunnerService server, Pipelines.AgentJobRequestMessage message)
|
||||
{
|
||||
try
|
||||
if (server is IJobServer jobServer)
|
||||
{
|
||||
var jobCompletedEvent = new JobCompletedEvent(message.RequestId, message.JobId, TaskResult.Failed);
|
||||
await jobServer.RaisePlanEventAsync<JobCompletedEvent>(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, jobCompletedEvent, CancellationToken.None);
|
||||
try
|
||||
{
|
||||
var jobCompletedEvent = new JobCompletedEvent(message.RequestId, message.JobId, TaskResult.Failed);
|
||||
await jobServer.RaisePlanEventAsync<JobCompletedEvent>(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, jobCompletedEvent, CancellationToken.None);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error("Fail to raise JobCompletedEvent back to service.");
|
||||
Trace.Error(ex);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
else if (server is IRunServer runServer)
|
||||
{
|
||||
Trace.Error("Fail to raise JobCompletedEvent back to service.");
|
||||
Trace.Error(ex);
|
||||
try
|
||||
{
|
||||
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, TaskResult.Failed, outputs: null, stepResults: null, CancellationToken.None);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error("Fail to raise job completion back to service.");
|
||||
Trace.Error(ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Server type {server.GetType().FullName} is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IRunnerService> InitializeJobServerAsync(ServiceEndpoint systemConnection)
|
||||
{
|
||||
if (this._isRunServiceJob)
|
||||
{
|
||||
return await GetRunServerAsync(systemConnection);
|
||||
}
|
||||
else
|
||||
{
|
||||
var jobServer = HostContext.GetService<IJobServer>();
|
||||
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
|
||||
VssConnection jobConnection = VssUtil.CreateConnection(systemConnection.Url, jobServerCredential);
|
||||
await jobServer.ConnectAsync(jobConnection);
|
||||
return jobServer;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IRunServer> GetRunServerAsync(ServiceEndpoint systemConnection)
|
||||
{
|
||||
var runServer = HostContext.GetService<IRunServer>();
|
||||
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
|
||||
await runServer.ConnectAsync(systemConnection.Url, jobServerCredential);
|
||||
return runServer;
|
||||
}
|
||||
|
||||
private class WorkerDispatcher : IDisposable
|
||||
@@ -1039,7 +1237,7 @@ namespace GitHub.Runner.Listener
|
||||
public Task WorkerDispatch { get; set; }
|
||||
public CancellationTokenSource WorkerCancellationTokenSource { get; private set; }
|
||||
public CancellationTokenSource WorkerCancelTimeoutKillTokenSource { get; private set; }
|
||||
private readonly object _lock = new object();
|
||||
private readonly object _lock = new();
|
||||
|
||||
public WorkerDispatcher(Guid jobId, long requestId)
|
||||
{
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace GitHub.Runner.Listener
|
||||
private readonly TimeSpan _sessionCreationRetryInterval = TimeSpan.FromSeconds(30);
|
||||
private readonly TimeSpan _sessionConflictRetryLimit = TimeSpan.FromMinutes(4);
|
||||
private readonly TimeSpan _clockSkewRetryLimit = TimeSpan.FromMinutes(30);
|
||||
private readonly Dictionary<string, int> _sessionCreationExceptionTracker = new Dictionary<string, int>();
|
||||
private readonly Dictionary<string, int> _sessionCreationExceptionTracker = new();
|
||||
private TaskAgentStatus runnerStatus = TaskAgentStatus.Online;
|
||||
private CancellationTokenSource _getMessagesTokenSource;
|
||||
|
||||
@@ -198,7 +198,7 @@ namespace GitHub.Runner.Listener
|
||||
bool encounteringError = false;
|
||||
int continuousError = 0;
|
||||
string errorMessage = string.Empty;
|
||||
Stopwatch heartbeat = new Stopwatch();
|
||||
Stopwatch heartbeat = new();
|
||||
heartbeat.Restart();
|
||||
while (true)
|
||||
{
|
||||
@@ -211,6 +211,7 @@ namespace GitHub.Runner.Listener
|
||||
_session.SessionId,
|
||||
_lastMessageId,
|
||||
runnerStatus,
|
||||
BuildConstants.RunnerPackage.Version,
|
||||
_getMessagesTokenSource.Token);
|
||||
|
||||
// Decrypt the message body if the session is using encryption
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace GitHub.Runner.Listener
|
||||
// Add environment variables from .env file
|
||||
LoadAndSetEnv();
|
||||
|
||||
using (HostContext context = new HostContext("Runner"))
|
||||
using (HostContext context = new("Runner"))
|
||||
{
|
||||
return MainAsync(context, args).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Listener.Check;
|
||||
using GitHub.Runner.Listener.Configuration;
|
||||
using GitHub.Runner.Sdk;
|
||||
@@ -28,7 +29,7 @@ namespace GitHub.Runner.Listener
|
||||
private IMessageListener _listener;
|
||||
private ITerminal _term;
|
||||
private bool _inConfigStage;
|
||||
private ManualResetEvent _completedCommand = new ManualResetEvent(false);
|
||||
private ManualResetEvent _completedCommand = new(false);
|
||||
|
||||
public override void Initialize(IHostContext hostContext)
|
||||
{
|
||||
@@ -135,6 +136,12 @@ namespace GitHub.Runner.Listener
|
||||
// remove config files, remove service, and exit
|
||||
if (command.Remove)
|
||||
{
|
||||
// only remove local config files and exit
|
||||
if (command.RemoveLocalConfig)
|
||||
{
|
||||
configManager.DeleteLocalRunnerConfig();
|
||||
return Constants.Runner.ReturnCode.Success;
|
||||
}
|
||||
try
|
||||
{
|
||||
await configManager.UnconfigureAsync(command);
|
||||
@@ -430,12 +437,22 @@ namespace GitHub.Runner.Listener
|
||||
|
||||
message = await getNextMessage; //get next message
|
||||
HostContext.WritePerfCounter($"MessageReceived_{message.MessageType}");
|
||||
if (string.Equals(message.MessageType, AgentRefreshMessage.MessageType, StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(message.MessageType, AgentRefreshMessage.MessageType, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(message.MessageType, RunnerRefreshMessage.MessageType, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (autoUpdateInProgress == false)
|
||||
{
|
||||
autoUpdateInProgress = true;
|
||||
var runnerUpdateMessage = JsonUtility.FromString<AgentRefreshMessage>(message.Body);
|
||||
AgentRefreshMessage runnerUpdateMessage = null;
|
||||
if (string.Equals(message.MessageType, AgentRefreshMessage.MessageType, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
runnerUpdateMessage = JsonUtility.FromString<AgentRefreshMessage>(message.Body);
|
||||
}
|
||||
else
|
||||
{
|
||||
var brokerRunnerUpdateMessage = JsonUtility.FromString<RunnerRefreshMessage>(message.Body);
|
||||
runnerUpdateMessage = new AgentRefreshMessage(brokerRunnerUpdateMessage.RunnerId, brokerRunnerUpdateMessage.TargetVersion, TimeSpan.FromSeconds(brokerRunnerUpdateMessage.TimeoutInSeconds));
|
||||
}
|
||||
#if DEBUG
|
||||
// Can mock the update for testing
|
||||
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_IS_MOCK_UPDATE")))
|
||||
@@ -486,7 +503,7 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
}
|
||||
// Broker flow
|
||||
else if (string.Equals(message.MessageType, JobRequestMessageTypes.RunnerJobRequest, StringComparison.OrdinalIgnoreCase))
|
||||
else if (MessageUtil.IsRunServiceJob(message.MessageType))
|
||||
{
|
||||
if (autoUpdateInProgress || runOnceJobReceived)
|
||||
{
|
||||
@@ -496,16 +513,26 @@ namespace GitHub.Runner.Listener
|
||||
else
|
||||
{
|
||||
var messageRef = StringUtil.ConvertFromJson<RunnerJobRequestRef>(message.Body);
|
||||
Pipelines.AgentJobRequestMessage jobRequestMessage = null;
|
||||
|
||||
// Create connection
|
||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
||||
var creds = credMgr.LoadCredentials();
|
||||
|
||||
var runServer = HostContext.CreateService<IRunServer>();
|
||||
await runServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
||||
var jobMessage = await runServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
||||
if (string.IsNullOrEmpty(messageRef.RunServiceUrl))
|
||||
{
|
||||
var actionsRunServer = HostContext.CreateService<IActionsRunServer>();
|
||||
await actionsRunServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
||||
jobRequestMessage = await actionsRunServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
||||
}
|
||||
else
|
||||
{
|
||||
var runServer = HostContext.CreateService<IRunServer>();
|
||||
await runServer.ConnectAsync(new Uri(messageRef.RunServiceUrl), creds);
|
||||
jobRequestMessage = await runServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
||||
}
|
||||
|
||||
jobDispatcher.Run(jobMessage, runOnce);
|
||||
jobDispatcher.Run(jobRequestMessage, runOnce);
|
||||
if (runOnce)
|
||||
{
|
||||
Trace.Info("One time used runner received job message.");
|
||||
@@ -627,6 +654,7 @@ Config Options:
|
||||
--name string Name of the runner to configure (default {Environment.MachineName ?? "myrunner"})
|
||||
--runnergroup string Name of the runner group to add this runner to (defaults to the default runner group)
|
||||
--labels string Extra labels in addition to the default: 'self-hosted,{Constants.Runner.Platform},{Constants.Runner.PlatformArchitecture}'
|
||||
--local Removes the runner config files from your local machine. Used as an option to the remove command
|
||||
--work string Relative runner work directory (default {Constants.Path.WorkDirectory})
|
||||
--replace Replace any existing runner with the same name (default false)
|
||||
--pat GitHub personal access token with repo scope. Used for checking network connectivity when executing `.{separator}run.{ext} --check`
|
||||
|
||||
@@ -9,5 +9,7 @@ namespace GitHub.Runner.Listener
|
||||
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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,14 +32,14 @@ namespace GitHub.Runner.Listener
|
||||
private static string _platform = BuildConstants.RunnerPackage.PackageName;
|
||||
private static string _dotnetRuntime = "dotnetRuntime";
|
||||
private static string _externals = "externals";
|
||||
private readonly Dictionary<string, string> _contentHashes = new Dictionary<string, string>();
|
||||
private readonly Dictionary<string, string> _contentHashes = new();
|
||||
|
||||
private PackageMetadata _targetPackage;
|
||||
private ITerminal _terminal;
|
||||
private IRunnerServer _runnerServer;
|
||||
private int _poolId;
|
||||
private int _agentId;
|
||||
private readonly ConcurrentQueue<string> _updateTrace = new ConcurrentQueue<string>();
|
||||
private readonly ConcurrentQueue<string> _updateTrace = new();
|
||||
private Task _cloneAndCalculateContentHashTask;
|
||||
private string _dotnetRuntimeCloneDirectory;
|
||||
private string _externalsCloneDirectory;
|
||||
@@ -134,7 +134,7 @@ namespace GitHub.Runner.Listener
|
||||
string flagFile = "update.finished";
|
||||
IOUtil.DeleteFile(flagFile);
|
||||
// kick off update script
|
||||
Process invokeScript = new Process();
|
||||
Process invokeScript = new();
|
||||
#if OS_WINDOWS
|
||||
invokeScript.StartInfo.FileName = WhichUtil.Which("cmd.exe", trace: Trace);
|
||||
invokeScript.StartInfo.Arguments = $"/c \"{updateScript}\"";
|
||||
@@ -191,9 +191,9 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
|
||||
Trace.Info($"Version '{_targetPackage.Version}' of '{_targetPackage.Type}' package available in server.");
|
||||
PackageVersion serverVersion = new PackageVersion(_targetPackage.Version);
|
||||
PackageVersion serverVersion = new(_targetPackage.Version);
|
||||
Trace.Info($"Current running runner version is {BuildConstants.RunnerPackage.Version}");
|
||||
PackageVersion runnerVersion = new PackageVersion(BuildConstants.RunnerPackage.Version);
|
||||
PackageVersion runnerVersion = new(BuildConstants.RunnerPackage.Version);
|
||||
|
||||
return serverVersion.CompareTo(runnerVersion) > 0;
|
||||
}
|
||||
@@ -476,7 +476,7 @@ namespace GitHub.Runner.Listener
|
||||
long downloadSize = 0;
|
||||
|
||||
//open zip stream in async mode
|
||||
using (HttpClient httpClient = new HttpClient(HostContext.CreateHttpClientHandler()))
|
||||
using (HttpClient httpClient = new(HostContext.CreateHttpClientHandler()))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_targetPackage.Token))
|
||||
{
|
||||
@@ -486,7 +486,7 @@ namespace GitHub.Runner.Listener
|
||||
|
||||
Trace.Info($"Downloading {packageDownloadUrl}");
|
||||
|
||||
using (FileStream fs = new FileStream(archiveFile, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true))
|
||||
using (FileStream fs = new(archiveFile, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true))
|
||||
using (Stream result = await httpClient.GetStreamAsync(packageDownloadUrl))
|
||||
{
|
||||
//81920 is the default used by System.IO.Stream.CopyTo and is under the large object heap threshold (85k).
|
||||
@@ -596,7 +596,7 @@ namespace GitHub.Runner.Listener
|
||||
int exitCode = await processInvoker.ExecuteAsync(extractDirectory, tar, $"-xzf \"{archiveFile}\"", null, token);
|
||||
if (exitCode != 0)
|
||||
{
|
||||
throw new NotSupportedException($"Can't use 'tar -xzf' extract archive file: {archiveFile}. return code: {exitCode}.");
|
||||
throw new NotSupportedException($"Can't use 'tar -xzf' to extract archive file: {archiveFile}. return code: {exitCode}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace GitHub.Runner.PluginHost
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
private static CancellationTokenSource tokenSource = new CancellationTokenSource();
|
||||
private static CancellationTokenSource tokenSource = new();
|
||||
private static string executingAssemblyLocation = string.Empty;
|
||||
|
||||
public static int Main(string[] args)
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace GitHub.Runner.Plugins.Artifact
|
||||
string containerPath = actionsStorageArtifact.Name; // In actions storage artifacts, name equals the path
|
||||
long containerId = actionsStorageArtifact.ContainerId;
|
||||
|
||||
FileContainerServer fileContainerServer = new FileContainerServer(context.VssConnection, projectId: new Guid(), containerId, containerPath);
|
||||
FileContainerServer fileContainerServer = new(context.VssConnection, projectId: new Guid(), containerId, containerPath);
|
||||
await fileContainerServer.DownloadFromContainerAsync(context, targetPath, token);
|
||||
|
||||
context.Output("Artifact download finished.");
|
||||
|
||||
@@ -23,10 +23,10 @@ namespace GitHub.Runner.Plugins.Artifact
|
||||
//81920 is the default used by System.IO.Stream.CopyTo and is under the large object heap threshold (85k).
|
||||
private const int _defaultCopyBufferSize = 81920;
|
||||
|
||||
private readonly ConcurrentQueue<string> _fileUploadQueue = new ConcurrentQueue<string>();
|
||||
private readonly ConcurrentQueue<DownloadInfo> _fileDownloadQueue = new ConcurrentQueue<DownloadInfo>();
|
||||
private readonly ConcurrentDictionary<string, ConcurrentQueue<string>> _fileUploadTraceLog = new ConcurrentDictionary<string, ConcurrentQueue<string>>();
|
||||
private readonly ConcurrentDictionary<string, ConcurrentQueue<string>> _fileUploadProgressLog = new ConcurrentDictionary<string, ConcurrentQueue<string>>();
|
||||
private readonly ConcurrentQueue<string> _fileUploadQueue = new();
|
||||
private readonly ConcurrentQueue<DownloadInfo> _fileDownloadQueue = new();
|
||||
private readonly ConcurrentDictionary<string, ConcurrentQueue<string>> _fileUploadTraceLog = new();
|
||||
private readonly ConcurrentDictionary<string, ConcurrentQueue<string>> _fileUploadProgressLog = new();
|
||||
private readonly FileContainerHttpClient _fileContainerHttpClient;
|
||||
|
||||
private CancellationTokenSource _uploadCancellationTokenSource;
|
||||
@@ -67,7 +67,7 @@ namespace GitHub.Runner.Plugins.Artifact
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Find out all container items need to be processed
|
||||
List<FileContainerItem> containerItems = new List<FileContainerItem>();
|
||||
List<FileContainerItem> containerItems = new();
|
||||
int retryCount = 0;
|
||||
while (retryCount < 3)
|
||||
{
|
||||
@@ -106,7 +106,7 @@ namespace GitHub.Runner.Plugins.Artifact
|
||||
// Create all required empty folders and emptry files, gather a list of files that we need to download from server.
|
||||
int foldersCreated = 0;
|
||||
int emptryFilesCreated = 0;
|
||||
List<DownloadInfo> downloadFiles = new List<DownloadInfo>();
|
||||
List<DownloadInfo> downloadFiles = new();
|
||||
foreach (var item in containerItems.OrderBy(x => x.Path))
|
||||
{
|
||||
if (!item.Path.StartsWith(_containerPath, StringComparison.OrdinalIgnoreCase))
|
||||
@@ -306,7 +306,7 @@ namespace GitHub.Runner.Plugins.Artifact
|
||||
Task downloadMonitor = DownloadReportingAsync(context, files.Count(), token);
|
||||
|
||||
// Start parallel download tasks.
|
||||
List<Task<DownloadResult>> parallelDownloadingTasks = new List<Task<DownloadResult>>();
|
||||
List<Task<DownloadResult>> parallelDownloadingTasks = new();
|
||||
for (int downloader = 0; downloader < concurrentDownloads; downloader++)
|
||||
{
|
||||
parallelDownloadingTasks.Add(DownloadAsync(context, downloader, token));
|
||||
@@ -358,7 +358,7 @@ namespace GitHub.Runner.Plugins.Artifact
|
||||
Task uploadMonitor = UploadReportingAsync(context, files.Count(), _uploadCancellationTokenSource.Token);
|
||||
|
||||
// Start parallel upload tasks.
|
||||
List<Task<UploadResult>> parallelUploadingTasks = new List<Task<UploadResult>>();
|
||||
List<Task<UploadResult>> parallelUploadingTasks = new();
|
||||
for (int uploader = 0; uploader < concurrentUploads; uploader++)
|
||||
{
|
||||
parallelUploadingTasks.Add(UploadAsync(context, uploader, _uploadCancellationTokenSource.Token));
|
||||
@@ -381,8 +381,8 @@ namespace GitHub.Runner.Plugins.Artifact
|
||||
|
||||
private async Task<DownloadResult> DownloadAsync(RunnerActionPluginExecutionContext context, int downloaderId, CancellationToken token)
|
||||
{
|
||||
List<DownloadInfo> failedFiles = new List<DownloadInfo>();
|
||||
Stopwatch downloadTimer = new Stopwatch();
|
||||
List<DownloadInfo> failedFiles = new();
|
||||
Stopwatch downloadTimer = new();
|
||||
while (_fileDownloadQueue.TryDequeue(out DownloadInfo fileToDownload))
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
@@ -396,7 +396,7 @@ namespace GitHub.Runner.Plugins.Artifact
|
||||
{
|
||||
context.Debug($"Start downloading file: '{fileToDownload.ItemPath}' (Downloader {downloaderId})");
|
||||
downloadTimer.Restart();
|
||||
using (FileStream fs = new FileStream(fileToDownload.LocalPath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: _defaultFileStreamBufferSize, useAsync: true))
|
||||
using (FileStream fs = new(fileToDownload.LocalPath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: _defaultFileStreamBufferSize, useAsync: true))
|
||||
using (var downloadStream = await _fileContainerHttpClient.DownloadFileAsync(_containerId, fileToDownload.ItemPath, token, _projectId))
|
||||
{
|
||||
await downloadStream.CopyToAsync(fs, _defaultCopyBufferSize, token);
|
||||
@@ -453,10 +453,10 @@ namespace GitHub.Runner.Plugins.Artifact
|
||||
|
||||
private async Task<UploadResult> UploadAsync(RunnerActionPluginExecutionContext context, int uploaderId, CancellationToken token)
|
||||
{
|
||||
List<string> failedFiles = new List<string>();
|
||||
List<string> failedFiles = new();
|
||||
long uploadedSize = 0;
|
||||
string fileToUpload;
|
||||
Stopwatch uploadTimer = new Stopwatch();
|
||||
Stopwatch uploadTimer = new();
|
||||
while (_fileUploadQueue.TryDequeue(out fileToUpload))
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace GitHub.Runner.Plugins.Artifact
|
||||
|
||||
context.Output($"Uploading artifact '{artifactName}' from '{fullPath}' for run #{buildId}");
|
||||
|
||||
FileContainerServer fileContainerHelper = new FileContainerServer(context.VssConnection, projectId: Guid.Empty, containerId, artifactName);
|
||||
FileContainerServer fileContainerHelper = new(context.VssConnection, projectId: Guid.Empty, containerId, artifactName);
|
||||
var propertiesDictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
long size = 0;
|
||||
@@ -87,7 +87,7 @@ namespace GitHub.Runner.Plugins.Artifact
|
||||
// Definition ID is a dummy value only used by HTTP client routing purposes
|
||||
int definitionId = 1;
|
||||
|
||||
PipelinesServer pipelinesHelper = new PipelinesServer(context.VssConnection);
|
||||
PipelinesServer pipelinesHelper = new(context.VssConnection);
|
||||
|
||||
var artifact = await pipelinesHelper.AssociateActionsStorageArtifactAsync(
|
||||
definitionId,
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace GitHub.Runner.Plugins.Repository
|
||||
#else
|
||||
private static readonly Encoding s_encoding = null;
|
||||
#endif
|
||||
private readonly Dictionary<string, string> gitEnv = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
private readonly Dictionary<string, string> gitEnv = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "GIT_TERMINAL_PROMPT", "0" },
|
||||
};
|
||||
@@ -92,11 +92,11 @@ namespace GitHub.Runner.Plugins.Repository
|
||||
}
|
||||
|
||||
// required 2.0, all git operation commandline args need min git version 2.0
|
||||
Version minRequiredGitVersion = new Version(2, 0);
|
||||
Version minRequiredGitVersion = new(2, 0);
|
||||
EnsureGitVersion(minRequiredGitVersion, throwOnNotMatch: true);
|
||||
|
||||
// suggest user upgrade to 2.9 for better git experience
|
||||
Version recommendGitVersion = new Version(2, 9);
|
||||
Version recommendGitVersion = new(2, 9);
|
||||
if (!EnsureGitVersion(recommendGitVersion, throwOnNotMatch: false))
|
||||
{
|
||||
context.Output($"To get a better Git experience, upgrade your Git to at least version '{recommendGitVersion}'. Your current Git version is '{gitVersion}'.");
|
||||
@@ -430,7 +430,7 @@ namespace GitHub.Runner.Plugins.Repository
|
||||
context.Debug($"Inspect remote.origin.url for repository under {repositoryPath}");
|
||||
Uri fetchUrl = null;
|
||||
|
||||
List<string> outputStrings = new List<string>();
|
||||
List<string> outputStrings = new();
|
||||
int exitCode = await ExecuteGitCommandAsync(context, repositoryPath, "config", "--get remote.origin.url", outputStrings);
|
||||
|
||||
if (exitCode != 0)
|
||||
@@ -477,7 +477,7 @@ namespace GitHub.Runner.Plugins.Repository
|
||||
context.Debug($"Checking git config {configKey} exist or not");
|
||||
|
||||
// ignore any outputs by redirect them into a string list, since the output might contains secrets.
|
||||
List<string> outputStrings = new List<string>();
|
||||
List<string> outputStrings = new();
|
||||
int exitcode = await ExecuteGitCommandAsync(context, repositoryPath, "config", StringUtil.Format($"--get-all {configKey}"), outputStrings);
|
||||
|
||||
return exitcode == 0;
|
||||
@@ -539,7 +539,7 @@ namespace GitHub.Runner.Plugins.Repository
|
||||
string runnerWorkspace = context.GetRunnerContext("workspace");
|
||||
ArgUtil.Directory(runnerWorkspace, "runnerWorkspace");
|
||||
Version version = null;
|
||||
List<string> outputStrings = new List<string>();
|
||||
List<string> outputStrings = new();
|
||||
int exitCode = await ExecuteGitCommandAsync(context, runnerWorkspace, "version", null, outputStrings);
|
||||
context.Output($"{string.Join(Environment.NewLine, outputStrings)}");
|
||||
if (exitCode == 0)
|
||||
@@ -550,7 +550,7 @@ namespace GitHub.Runner.Plugins.Repository
|
||||
{
|
||||
string verString = outputStrings.First();
|
||||
// we interested about major.minor.patch version
|
||||
Regex verRegex = new Regex("\\d+\\.\\d+(\\.\\d+)?", RegexOptions.IgnoreCase);
|
||||
Regex verRegex = new("\\d+\\.\\d+(\\.\\d+)?", RegexOptions.IgnoreCase);
|
||||
var matchResult = verRegex.Match(verString);
|
||||
if (matchResult.Success && !string.IsNullOrEmpty(matchResult.Value))
|
||||
{
|
||||
@@ -572,7 +572,7 @@ namespace GitHub.Runner.Plugins.Repository
|
||||
string runnerWorkspace = context.GetRunnerContext("workspace");
|
||||
ArgUtil.Directory(runnerWorkspace, "runnerWorkspace");
|
||||
Version version = null;
|
||||
List<string> outputStrings = new List<string>();
|
||||
List<string> outputStrings = new();
|
||||
int exitCode = await ExecuteGitCommandAsync(context, runnerWorkspace, "lfs version", null, outputStrings);
|
||||
context.Output($"{string.Join(Environment.NewLine, outputStrings)}");
|
||||
if (exitCode == 0)
|
||||
@@ -583,7 +583,7 @@ namespace GitHub.Runner.Plugins.Repository
|
||||
{
|
||||
string verString = outputStrings.First();
|
||||
// we interested about major.minor.patch version
|
||||
Regex verRegex = new Regex("\\d+\\.\\d+(\\.\\d+)?", RegexOptions.IgnoreCase);
|
||||
Regex verRegex = new("\\d+\\.\\d+(\\.\\d+)?", RegexOptions.IgnoreCase);
|
||||
var matchResult = verRegex.Match(verString);
|
||||
if (matchResult.Success && !string.IsNullOrEmpty(matchResult.Value))
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
|
||||
private const string _remotePullRefsPrefix = "refs/remotes/pull/";
|
||||
|
||||
// min git version that support add extra auth header.
|
||||
private Version _minGitVersionSupportAuthHeader = new Version(2, 9);
|
||||
private Version _minGitVersionSupportAuthHeader = new(2, 9);
|
||||
|
||||
#if OS_WINDOWS
|
||||
// min git version that support override sslBackend setting.
|
||||
@@ -29,7 +29,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
|
||||
#endif
|
||||
|
||||
// min git-lfs version that support add extra auth header.
|
||||
private Version _minGitLfsVersionSupportAuthHeader = new Version(2, 1);
|
||||
private Version _minGitLfsVersionSupportAuthHeader = new(2, 1);
|
||||
|
||||
private void RequirementCheck(RunnerActionPluginExecutionContext executionContext, GitCliManager gitCommandManager, bool checkGitLfs)
|
||||
{
|
||||
@@ -83,7 +83,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
|
||||
var githubUrl = executionContext.GetGitHubContext("server_url");
|
||||
var githubUri = new Uri(!string.IsNullOrEmpty(githubUrl) ? githubUrl : "https://github.com");
|
||||
var portInfo = githubUri.IsDefaultPort ? string.Empty : $":{githubUri.Port}";
|
||||
Uri repositoryUrl = new Uri($"{githubUri.Scheme}://{githubUri.Host}{portInfo}/{repoFullName}");
|
||||
Uri repositoryUrl = new($"{githubUri.Scheme}://{githubUri.Host}{portInfo}/{repoFullName}");
|
||||
if (!repositoryUrl.IsAbsoluteUri)
|
||||
{
|
||||
throw new InvalidOperationException("Repository url need to be an absolute uri.");
|
||||
@@ -121,7 +121,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
|
||||
executionContext.Debug($"gitLfsSupport={gitLfsSupport}");
|
||||
|
||||
// Initialize git command manager with additional environment variables.
|
||||
Dictionary<string, string> gitEnv = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
Dictionary<string, string> gitEnv = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Disable prompting for git credential manager
|
||||
gitEnv["GCM_INTERACTIVE"] = "Never";
|
||||
@@ -141,7 +141,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
|
||||
gitEnv[formattedKey] = variable.Value?.Value ?? string.Empty;
|
||||
}
|
||||
|
||||
GitCliManager gitCommandManager = new GitCliManager(gitEnv);
|
||||
GitCliManager gitCommandManager = new(gitEnv);
|
||||
await gitCommandManager.LoadGitExecutionInfo(executionContext);
|
||||
|
||||
// Make sure the build machine met all requirements for the git repository
|
||||
@@ -293,8 +293,8 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
|
||||
await RemoveGitConfig(executionContext, gitCommandManager, targetPath, $"http.{repositoryUrl.AbsoluteUri}.extraheader", string.Empty);
|
||||
}
|
||||
|
||||
List<string> additionalFetchArgs = new List<string>();
|
||||
List<string> additionalLfsFetchArgs = new List<string>();
|
||||
List<string> additionalFetchArgs = new();
|
||||
List<string> additionalLfsFetchArgs = new();
|
||||
|
||||
// add accessToken as basic auth header to handle auth challenge.
|
||||
if (!string.IsNullOrEmpty(accessToken))
|
||||
@@ -320,7 +320,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
|
||||
}
|
||||
}
|
||||
|
||||
List<string> additionalFetchSpecs = new List<string>();
|
||||
List<string> additionalFetchSpecs = new();
|
||||
additionalFetchSpecs.Add("+refs/heads/*:refs/remotes/origin/*");
|
||||
|
||||
if (IsPullRequest(sourceBranch))
|
||||
@@ -395,7 +395,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
|
||||
throw new InvalidOperationException($"Git submodule sync failed with exit code: {exitCode_submoduleSync}");
|
||||
}
|
||||
|
||||
List<string> additionalSubmoduleUpdateArgs = new List<string>();
|
||||
List<string> additionalSubmoduleUpdateArgs = new();
|
||||
|
||||
if (!string.IsNullOrEmpty(accessToken))
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
|
||||
{
|
||||
public class CheckoutTask : IRunnerActionPlugin
|
||||
{
|
||||
private readonly Regex _validSha1 = new Regex(@"\b[0-9a-f]{40}\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled, TimeSpan.FromSeconds(2));
|
||||
private readonly Regex _validSha1 = new(@"\b[0-9a-f]{40}\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled, TimeSpan.FromSeconds(2));
|
||||
|
||||
public async Task RunAsync(RunnerActionPluginExecutionContext executionContext, CancellationToken token)
|
||||
{
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
|
||||
private const string _tagRefsPrefix = "refs/tags/";
|
||||
|
||||
// min git version that support add extra auth header.
|
||||
private Version _minGitVersionSupportAuthHeader = new Version(2, 9);
|
||||
private Version _minGitVersionSupportAuthHeader = new(2, 9);
|
||||
|
||||
#if OS_WINDOWS
|
||||
// min git version that support override sslBackend setting.
|
||||
@@ -30,7 +30,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
|
||||
#endif
|
||||
|
||||
// min git-lfs version that support add extra auth header.
|
||||
private Version _minGitLfsVersionSupportAuthHeader = new Version(2, 1);
|
||||
private Version _minGitLfsVersionSupportAuthHeader = new(2, 1);
|
||||
|
||||
public static string ProblemMatcher => @"
|
||||
{
|
||||
@@ -62,9 +62,9 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
|
||||
{
|
||||
// Validate args.
|
||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
||||
Dictionary<string, string> configModifications = new Dictionary<string, string>();
|
||||
Dictionary<string, string> configModifications = new();
|
||||
executionContext.Output($"Syncing repository: {repoFullName}");
|
||||
Uri repositoryUrl = new Uri($"https://github.com/{repoFullName}");
|
||||
Uri repositoryUrl = new($"https://github.com/{repoFullName}");
|
||||
if (!repositoryUrl.IsAbsoluteUri)
|
||||
{
|
||||
throw new InvalidOperationException("Repository url need to be an absolute uri.");
|
||||
@@ -102,7 +102,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
|
||||
executionContext.Debug($"gitLfsSupport={gitLfsSupport}");
|
||||
|
||||
// Initialize git command manager with additional environment variables.
|
||||
Dictionary<string, string> gitEnv = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
Dictionary<string, string> gitEnv = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Disable git prompt
|
||||
gitEnv["GIT_TERMINAL_PROMPT"] = "0";
|
||||
@@ -125,7 +125,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
|
||||
gitEnv[formattedKey] = variable.Value?.Value ?? string.Empty;
|
||||
}
|
||||
|
||||
GitCliManager gitCommandManager = new GitCliManager(gitEnv);
|
||||
GitCliManager gitCommandManager = new(gitEnv);
|
||||
await gitCommandManager.LoadGitExecutionInfo(executionContext);
|
||||
|
||||
// Make sure the build machine met all requirements for the git repository
|
||||
@@ -277,8 +277,8 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
|
||||
await RemoveGitConfig(executionContext, gitCommandManager, targetPath, $"http.{repositoryUrl.AbsoluteUri}.extraheader", string.Empty);
|
||||
}
|
||||
|
||||
List<string> additionalFetchArgs = new List<string>();
|
||||
List<string> additionalLfsFetchArgs = new List<string>();
|
||||
List<string> additionalFetchArgs = new();
|
||||
List<string> additionalLfsFetchArgs = new();
|
||||
|
||||
// Add http.https://github.com.extraheader=... to gitconfig
|
||||
// accessToken as basic auth header to handle any auth challenge from github.com
|
||||
@@ -303,7 +303,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
|
||||
}
|
||||
}
|
||||
|
||||
List<string> additionalFetchSpecs = new List<string>();
|
||||
List<string> additionalFetchSpecs = new();
|
||||
additionalFetchSpecs.Add("+refs/heads/*:refs/remotes/origin/*");
|
||||
|
||||
if (IsPullRequest(sourceBranch))
|
||||
@@ -378,7 +378,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
|
||||
throw new InvalidOperationException($"Git submodule sync failed with exit code: {exitCode_submoduleSync}");
|
||||
}
|
||||
|
||||
List<string> additionalSubmoduleUpdateArgs = new List<string>();
|
||||
List<string> additionalSubmoduleUpdateArgs = new();
|
||||
|
||||
int exitCode_submoduleUpdate = await gitCommandManager.GitSubmoduleUpdate(executionContext, targetPath, fetchDepth, string.Join(" ", additionalSubmoduleUpdateArgs), checkoutNestedSubmodules, cancellationToken);
|
||||
if (exitCode_submoduleUpdate != 0)
|
||||
@@ -404,7 +404,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
|
||||
executionContext.Output($"Cleanup cached git credential from {repositoryPath}.");
|
||||
|
||||
// Initialize git command manager
|
||||
GitCliManager gitCommandManager = new GitCliManager();
|
||||
GitCliManager gitCommandManager = new();
|
||||
await gitCommandManager.LoadGitExecutionInfo(executionContext);
|
||||
|
||||
executionContext.Debug("Remove any extraheader setting from git config.");
|
||||
@@ -499,7 +499,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
|
||||
string gitConfig = Path.Combine(targetPath, ".git/config");
|
||||
if (File.Exists(gitConfig))
|
||||
{
|
||||
List<string> safeGitConfig = new List<string>();
|
||||
List<string> safeGitConfig = new();
|
||||
var gitConfigContents = File.ReadAllLines(gitConfig);
|
||||
foreach (var line in gitConfigContents)
|
||||
{
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace GitHub.Runner.Sdk
|
||||
private readonly string DebugEnvironmentalVariable = "ACTIONS_STEP_DEBUG";
|
||||
private VssConnection _connection;
|
||||
private RunnerWebProxy _webProxy;
|
||||
private readonly object _stdoutLock = new object();
|
||||
private readonly object _stdoutLock = new();
|
||||
private readonly ITraceWriter _trace; // for unit tests
|
||||
|
||||
public RunnerActionPluginExecutionContext()
|
||||
@@ -73,7 +73,7 @@ namespace GitHub.Runner.Sdk
|
||||
{
|
||||
var headerValues = new List<ProductInfoHeaderValue>();
|
||||
headerValues.Add(new ProductInfoHeaderValue($"GitHubActionsRunner-Plugin", BuildConstants.RunnerPackage.Version));
|
||||
headerValues.Add(new ProductInfoHeaderValue($"({RuntimeInformation.OSDescription.Trim()})"));
|
||||
headerValues.Add(new ProductInfoHeaderValue($"({StringUtil.SanitizeUserAgentHeader(RuntimeInformation.OSDescription)})"));
|
||||
|
||||
if (VssClientHttpRequestSettings.Default.UserAgent != null && VssClientHttpRequestSettings.Default.UserAgent.Count > 0)
|
||||
{
|
||||
@@ -220,7 +220,7 @@ namespace GitHub.Runner.Sdk
|
||||
return input;
|
||||
}
|
||||
|
||||
private Dictionary<string, string> _commandEscapeMappings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
private Dictionary<string, string> _commandEscapeMappings = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{
|
||||
";", "%3B"
|
||||
|
||||
@@ -24,18 +24,18 @@ namespace GitHub.Runner.Sdk
|
||||
private Stopwatch _stopWatch;
|
||||
private int _asyncStreamReaderCount = 0;
|
||||
private bool _waitingOnStreams = false;
|
||||
private readonly AsyncManualResetEvent _outputProcessEvent = new AsyncManualResetEvent();
|
||||
private readonly TaskCompletionSource<bool> _processExitedCompletionSource = new TaskCompletionSource<bool>();
|
||||
private readonly CancellationTokenSource _processStandardInWriteCancellationTokenSource = new CancellationTokenSource();
|
||||
private readonly ConcurrentQueue<string> _errorData = new ConcurrentQueue<string>();
|
||||
private readonly ConcurrentQueue<string> _outputData = new ConcurrentQueue<string>();
|
||||
private readonly AsyncManualResetEvent _outputProcessEvent = new();
|
||||
private readonly TaskCompletionSource<bool> _processExitedCompletionSource = new();
|
||||
private readonly CancellationTokenSource _processStandardInWriteCancellationTokenSource = new();
|
||||
private readonly ConcurrentQueue<string> _errorData = new();
|
||||
private readonly ConcurrentQueue<string> _outputData = new();
|
||||
private readonly TimeSpan _sigintTimeout = TimeSpan.FromMilliseconds(7500);
|
||||
private readonly TimeSpan _sigtermTimeout = TimeSpan.FromMilliseconds(2500);
|
||||
private ITraceWriter Trace { get; set; }
|
||||
|
||||
private class AsyncManualResetEvent
|
||||
{
|
||||
private volatile TaskCompletionSource<bool> m_tcs = new TaskCompletionSource<bool>();
|
||||
private volatile TaskCompletionSource<bool> m_tcs = new();
|
||||
|
||||
public Task WaitAsync() { return m_tcs.Task; }
|
||||
|
||||
@@ -264,7 +264,17 @@ namespace GitHub.Runner.Sdk
|
||||
{
|
||||
foreach (KeyValuePair<string, string> kvp in environment)
|
||||
{
|
||||
#if OS_WINDOWS
|
||||
string tempKey = String.IsNullOrWhiteSpace(kvp.Key) ? kvp.Key : kvp.Key.Split('\0')[0];
|
||||
string tempValue = String.IsNullOrWhiteSpace(kvp.Value) ? kvp.Value : kvp.Value.Split('\0')[0];
|
||||
if(!String.IsNullOrWhiteSpace(tempKey))
|
||||
{
|
||||
_proc.StartInfo.Environment[tempKey] = tempValue;
|
||||
}
|
||||
#else
|
||||
_proc.StartInfo.Environment[kvp.Key] = kvp.Value;
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,8 +397,8 @@ namespace GitHub.Runner.Sdk
|
||||
|
||||
private void ProcessOutput()
|
||||
{
|
||||
List<string> errorData = new List<string>();
|
||||
List<string> outputData = new List<string>();
|
||||
List<string> errorData = new();
|
||||
List<string> outputData = new();
|
||||
|
||||
string errorLine;
|
||||
while (_errorData.TryDequeue(out errorLine))
|
||||
|
||||
@@ -23,9 +23,9 @@ namespace GitHub.Runner.Sdk
|
||||
private string _httpsProxyPassword;
|
||||
private string _noProxyString;
|
||||
|
||||
private readonly List<ByPassInfo> _noProxyList = new List<ByPassInfo>();
|
||||
private readonly HashSet<string> _noProxyUnique = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Regex _validIpRegex = new Regex("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$", RegexOptions.Compiled);
|
||||
private readonly List<ByPassInfo> _noProxyList = new();
|
||||
private readonly HashSet<string> _noProxyUnique = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Regex _validIpRegex = new("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$", RegexOptions.Compiled);
|
||||
|
||||
public string HttpProxyAddress => _httpProxyAddress;
|
||||
public string HttpProxyUsername => _httpProxyUsername;
|
||||
@@ -164,7 +164,6 @@ namespace GitHub.Runner.Sdk
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_noProxyList.Add(noProxyInfo);
|
||||
}
|
||||
}
|
||||
@@ -207,6 +206,11 @@ namespace GitHub.Runner.Sdk
|
||||
{
|
||||
foreach (var noProxy in _noProxyList)
|
||||
{
|
||||
// bypass on wildcard no_proxy
|
||||
if (string.Equals(noProxy.Host, "*", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var matchHost = false;
|
||||
var matchPort = false;
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace GitHub.Runner.Sdk
|
||||
using (SHA256 sha256hash = SHA256.Create())
|
||||
{
|
||||
byte[] data = sha256hash.ComputeHash(Encoding.UTF8.GetBytes(hashString));
|
||||
StringBuilder sBuilder = new StringBuilder();
|
||||
StringBuilder sBuilder = new();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
sBuilder.Append(data[i].ToString("x2"));
|
||||
@@ -77,7 +77,7 @@ namespace GitHub.Runner.Sdk
|
||||
public static void DeleteDirectory(string path, bool contentsOnly, bool continueOnContentDeleteError, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgUtil.NotNullOrEmpty(path, nameof(path));
|
||||
DirectoryInfo directory = new DirectoryInfo(path);
|
||||
DirectoryInfo directory = new(path);
|
||||
if (!directory.Exists)
|
||||
{
|
||||
return;
|
||||
@@ -363,12 +363,12 @@ namespace GitHub.Runner.Sdk
|
||||
Directory.CreateDirectory(target);
|
||||
|
||||
// Get the file contents of the directory to copy.
|
||||
DirectoryInfo sourceDir = new DirectoryInfo(source);
|
||||
DirectoryInfo sourceDir = new(source);
|
||||
foreach (FileInfo sourceFile in sourceDir.GetFiles() ?? new FileInfo[0])
|
||||
{
|
||||
// Check if the file already exists.
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
FileInfo targetFile = new FileInfo(Path.Combine(target, sourceFile.Name));
|
||||
FileInfo targetFile = new(Path.Combine(target, sourceFile.Name));
|
||||
if (!targetFile.Exists ||
|
||||
sourceFile.Length != targetFile.Length ||
|
||||
sourceFile.LastWriteTime != targetFile.LastWriteTime)
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace GitHub.Runner.Sdk
|
||||
public static class StringUtil
|
||||
{
|
||||
private static readonly object[] s_defaultFormatArgs = new object[] { null };
|
||||
private static Lazy<JsonSerializerSettings> s_serializerSettings = new Lazy<JsonSerializerSettings>(() =>
|
||||
private static Lazy<JsonSerializerSettings> s_serializerSettings = new(() =>
|
||||
{
|
||||
var settings = new VssJsonMediaTypeFormatter().SerializerSettings;
|
||||
settings.DateParseHandling = DateParseHandling.None;
|
||||
@@ -123,5 +123,12 @@ namespace GitHub.Runner.Sdk
|
||||
{
|
||||
return value?.Substring(0, Math.Min(value.Length, count));
|
||||
}
|
||||
|
||||
// Fixes format violations e.g. https://github.com/actions/runner/issues/2165
|
||||
public static string SanitizeUserAgentHeader(string header)
|
||||
{
|
||||
return header.Replace("(", "[").Replace(")", "]").Trim();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,16 @@ namespace GitHub.Runner.Sdk
|
||||
{
|
||||
public static bool IsHostedServer(UriBuilder gitHubUrl)
|
||||
{
|
||||
return string.Equals(gitHubUrl.Host, "github.com", StringComparison.OrdinalIgnoreCase) ||
|
||||
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_FORCE_GHES")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return
|
||||
string.Equals(gitHubUrl.Host, "github.com", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(gitHubUrl.Host, "www.github.com", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(gitHubUrl.Host, "github.localhost", StringComparison.OrdinalIgnoreCase);
|
||||
string.Equals(gitHubUrl.Host, "github.localhost", StringComparison.OrdinalIgnoreCase) ||
|
||||
gitHubUrl.Host.EndsWith(".ghe.com", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static Uri GetCredentialEmbeddedUrl(Uri baseUrl, string username, string password)
|
||||
@@ -21,7 +28,7 @@ namespace GitHub.Runner.Sdk
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
UriBuilder credUri = new UriBuilder(baseUrl);
|
||||
UriBuilder credUri = new(baseUrl);
|
||||
|
||||
// ensure we have a username, uribuild will throw if username is empty but password is not.
|
||||
if (string.IsNullOrEmpty(username))
|
||||
|
||||
@@ -9,6 +9,7 @@ using GitHub.Services.OAuth;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Net;
|
||||
using Sdk.WebApi.WebApi.RawClient;
|
||||
|
||||
namespace GitHub.Runner.Sdk
|
||||
{
|
||||
@@ -18,7 +19,7 @@ namespace GitHub.Runner.Sdk
|
||||
{
|
||||
var headerValues = new List<ProductInfoHeaderValue>();
|
||||
headerValues.AddRange(additionalUserAgents);
|
||||
headerValues.Add(new ProductInfoHeaderValue($"({RuntimeInformation.OSDescription.Trim()})"));
|
||||
headerValues.Add(new ProductInfoHeaderValue($"({StringUtil.SanitizeUserAgentHeader(RuntimeInformation.OSDescription)})"));
|
||||
|
||||
if (VssClientHttpRequestSettings.Default.UserAgent != null && VssClientHttpRequestSettings.Default.UserAgent.Count > 0)
|
||||
{
|
||||
@@ -34,7 +35,11 @@ namespace GitHub.Runner.Sdk
|
||||
}
|
||||
}
|
||||
|
||||
public static VssConnection CreateConnection(Uri serverUri, VssCredentials credentials, IEnumerable<DelegatingHandler> additionalDelegatingHandler = null, TimeSpan? timeout = null)
|
||||
public static VssConnection CreateConnection(
|
||||
Uri serverUri,
|
||||
VssCredentials credentials,
|
||||
IEnumerable<DelegatingHandler> additionalDelegatingHandler = null,
|
||||
TimeSpan? timeout = null)
|
||||
{
|
||||
VssClientHttpRequestSettings settings = VssClientHttpRequestSettings.Default.Clone();
|
||||
|
||||
@@ -71,7 +76,47 @@ namespace GitHub.Runner.Sdk
|
||||
// settings are applied to an HttpRequestMessage.
|
||||
settings.AcceptLanguages.Remove(CultureInfo.InvariantCulture);
|
||||
|
||||
VssConnection connection = new VssConnection(serverUri, new VssHttpMessageHandler(credentials, settings), additionalDelegatingHandler);
|
||||
VssConnection connection = new(serverUri, new VssHttpMessageHandler(credentials, settings), additionalDelegatingHandler);
|
||||
return connection;
|
||||
}
|
||||
|
||||
public static RawConnection CreateRawConnection(
|
||||
Uri serverUri,
|
||||
VssCredentials credentials,
|
||||
IEnumerable<DelegatingHandler> additionalDelegatingHandler = null,
|
||||
TimeSpan? timeout = null)
|
||||
{
|
||||
RawClientHttpRequestSettings settings = RawClientHttpRequestSettings.Default.Clone();
|
||||
|
||||
int maxRetryRequest;
|
||||
if (!int.TryParse(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_HTTP_RETRY") ?? string.Empty, out maxRetryRequest))
|
||||
{
|
||||
maxRetryRequest = 3;
|
||||
}
|
||||
|
||||
// make sure MaxRetryRequest in range [3, 10]
|
||||
settings.MaxRetryRequest = Math.Min(Math.Max(maxRetryRequest, 3), 10);
|
||||
|
||||
if (!int.TryParse(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_HTTP_TIMEOUT") ?? string.Empty, out int httpRequestTimeoutSeconds))
|
||||
{
|
||||
settings.SendTimeout = timeout ?? TimeSpan.FromSeconds(100);
|
||||
}
|
||||
else
|
||||
{
|
||||
// prefer environment variable
|
||||
settings.SendTimeout = TimeSpan.FromSeconds(Math.Min(Math.Max(httpRequestTimeoutSeconds, 100), 1200));
|
||||
}
|
||||
|
||||
// Remove Invariant from the list of accepted languages.
|
||||
//
|
||||
// The constructor of VssHttpRequestSettings (base class of VssClientHttpRequestSettings) adds the current
|
||||
// UI culture to the list of accepted languages. The UI culture will be Invariant on OSX/Linux when the
|
||||
// LANG environment variable is not set when the program starts. If Invariant is in the list of accepted
|
||||
// languages, then "System.ArgumentException: The value cannot be null or empty." will be thrown when the
|
||||
// settings are applied to an HttpRequestMessage.
|
||||
settings.AcceptLanguages.Remove(CultureInfo.InvariantCulture);
|
||||
|
||||
RawConnection connection = new(serverUri, new RawHttpMessageHandler(credentials.Federated, settings), additionalDelegatingHandler);
|
||||
return connection;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user