mirror of
https://github.com/actions/runner.git
synced 2025-12-10 20:36:49 +00:00
Compare commits
57 Commits
v2.288.0
...
users/tako
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d1eb56adb | ||
|
|
fa0dd79c30 | ||
|
|
9623a44c2f | ||
|
|
b2e2aa68c8 | ||
|
|
a9ce6b92c4 | ||
|
|
a1bf8401d7 | ||
|
|
a7152f1370 | ||
|
|
af285115e7 | ||
|
|
0431b6fd40 | ||
|
|
c3d5449146 | ||
|
|
9c5300b5b2 | ||
|
|
183b1f387c | ||
|
|
42ad85741e | ||
|
|
88ee16fb02 | ||
|
|
5cca207314 | ||
|
|
0b73794267 | ||
|
|
d7694774a4 | ||
|
|
0398f57125 | ||
|
|
fade0f46e7 | ||
|
|
02b52e8497 | ||
|
|
628f462ab7 | ||
|
|
7ba4f8587e | ||
|
|
88f7c56757 | ||
|
|
20b7e86e47 | ||
|
|
bd5f275830 | ||
|
|
a7aadf5615 | ||
|
|
1c582abc8b | ||
|
|
44d4d076fe | ||
|
|
b6195624ac | ||
|
|
ead3509d5a | ||
|
|
fee24199cb | ||
|
|
c8cb600ac7 | ||
|
|
f48f314a70 | ||
|
|
7b677e0618 | ||
|
|
d70f9f6174 | ||
|
|
0343e76789 | ||
|
|
909b05eb66 | ||
|
|
2e3976cf97 | ||
|
|
052ac521b0 | ||
|
|
408d6c579c | ||
|
|
46258428cd | ||
|
|
eb9a604b63 | ||
|
|
8792d8e5ee | ||
|
|
87e86e3d72 | ||
|
|
48b6cd9a42 | ||
|
|
d081289ed5 | ||
|
|
7d5e9cd70f | ||
|
|
98aa9c1152 | ||
|
|
ddc700e9eb | ||
|
|
a0458aebfe | ||
|
|
b2c6d093b2 | ||
|
|
292a2e0ab3 | ||
|
|
29cee52276 | ||
|
|
ad0d0c4d0a | ||
|
|
2c6064a655 | ||
|
|
af6c8e6edd | ||
|
|
c15d3f10b2 |
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
runtime: [ linux-x64, linux-arm64, linux-arm, win-x64, osx-x64 ]
|
||||
runtime: [ linux-x64, linux-arm64, linux-arm, win-x64, osx-x64, osx-arm64 ]
|
||||
include:
|
||||
- runtime: linux-x64
|
||||
os: ubuntu-latest
|
||||
@@ -36,13 +36,17 @@ jobs:
|
||||
os: macOS-latest
|
||||
devScript: ./dev.sh
|
||||
|
||||
- runtime: osx-arm64
|
||||
os: macOS-latest
|
||||
devScript: ./dev.sh
|
||||
|
||||
- runtime: win-x64
|
||||
os: windows-2019
|
||||
devScript: ./dev
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# Build runner layout
|
||||
- name: Build & Layout Release
|
||||
@@ -78,7 +82,7 @@ jobs:
|
||||
run: |
|
||||
${{ matrix.devScript }} test
|
||||
working-directory: src
|
||||
if: matrix.runtime != 'linux-arm64' && matrix.runtime != 'linux-arm'
|
||||
if: matrix.runtime != 'linux-arm64' && matrix.runtime != 'linux-arm' && matrix.runtime != 'osx-arm64'
|
||||
|
||||
# Create runner package tar.gz/zip
|
||||
- name: Package Release
|
||||
|
||||
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
89
.github/workflows/release.yml
vendored
89
.github/workflows/release.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/heads/releases/') || github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# Make sure ./releaseVersion match ./src/runnerversion
|
||||
# Query GitHub release ensure version is not used
|
||||
@@ -51,24 +51,28 @@ jobs:
|
||||
linux-arm-sha: ${{ steps.sha.outputs.linux-arm-sha256 }}
|
||||
win-x64-sha: ${{ steps.sha.outputs.win-x64-sha256 }}
|
||||
osx-x64-sha: ${{ steps.sha.outputs.osx-x64-sha256 }}
|
||||
osx-arm64-sha: ${{ steps.sha.outputs.osx-arm64-sha256 }}
|
||||
linux-x64-sha-noexternals: ${{ steps.sha_noexternals.outputs.linux-x64-sha256 }}
|
||||
linux-arm64-sha-noexternals: ${{ steps.sha_noexternals.outputs.linux-arm64-sha256 }}
|
||||
linux-arm-sha-noexternals: ${{ steps.sha_noexternals.outputs.linux-arm-sha256 }}
|
||||
win-x64-sha-noexternals: ${{ steps.sha_noexternals.outputs.win-x64-sha256 }}
|
||||
osx-x64-sha-noexternals: ${{ steps.sha_noexternals.outputs.osx-x64-sha256 }}
|
||||
osx-arm64-sha-noexternals: ${{ steps.sha_noexternals.outputs.osx-arm64-sha256 }}
|
||||
linux-x64-sha-noruntime: ${{ steps.sha_noruntime.outputs.linux-x64-sha256 }}
|
||||
linux-arm64-sha-noruntime: ${{ steps.sha_noruntime.outputs.linux-arm64-sha256 }}
|
||||
linux-arm-sha-noruntime: ${{ steps.sha_noruntime.outputs.linux-arm-sha256 }}
|
||||
win-x64-sha-noruntime: ${{ steps.sha_noruntime.outputs.win-x64-sha256 }}
|
||||
osx-x64-sha-noruntime: ${{ steps.sha_noruntime.outputs.osx-x64-sha256 }}
|
||||
osx-arm64-sha-noruntime: ${{ steps.sha_noruntime.outputs.osx-arm64-sha256 }}
|
||||
linux-x64-sha-noruntime-noexternals: ${{ steps.sha_noruntime_noexternals.outputs.linux-x64-sha256 }}
|
||||
linux-arm64-sha-noruntime-noexternals: ${{ steps.sha_noruntime_noexternals.outputs.linux-arm64-sha256 }}
|
||||
linux-arm-sha-noruntime-noexternals: ${{ steps.sha_noruntime_noexternals.outputs.linux-arm-sha256 }}
|
||||
win-x64-sha-noruntime-noexternals: ${{ steps.sha_noruntime_noexternals.outputs.win-x64-sha256 }}
|
||||
osx-x64-sha-noruntime-noexternals: ${{ steps.sha_noruntime_noexternals.outputs.osx-x64-sha256 }}
|
||||
osx-arm64-sha-noruntime-noexternals: ${{ steps.sha_noruntime_noexternals.outputs.osx-arm64-sha256 }}
|
||||
strategy:
|
||||
matrix:
|
||||
runtime: [ linux-x64, linux-arm64, linux-arm, win-x64, osx-x64 ]
|
||||
runtime: [ linux-x64, linux-arm64, linux-arm, win-x64, osx-x64, osx-arm64 ]
|
||||
include:
|
||||
- runtime: linux-x64
|
||||
os: ubuntu-latest
|
||||
@@ -85,6 +89,10 @@ jobs:
|
||||
- runtime: osx-x64
|
||||
os: macOS-latest
|
||||
devScript: ./dev.sh
|
||||
|
||||
- runtime: osx-arm64
|
||||
os: macOS-latest
|
||||
devScript: ./dev.sh
|
||||
|
||||
- runtime: win-x64
|
||||
os: windows-2019
|
||||
@@ -92,7 +100,7 @@ jobs:
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# Build runner layout
|
||||
- name: Build & Layout Release
|
||||
@@ -100,13 +108,6 @@ jobs:
|
||||
${{ matrix.devScript }} layout Release ${{ matrix.runtime }}
|
||||
working-directory: src
|
||||
|
||||
# Run tests
|
||||
- name: L0
|
||||
run: |
|
||||
${{ matrix.devScript }} test
|
||||
working-directory: src
|
||||
if: matrix.runtime != 'linux-arm64' && matrix.runtime != 'linux-arm'
|
||||
|
||||
# Create runner package tar.gz/zip
|
||||
- name: Package Release
|
||||
if: github.event_name != 'pull_request'
|
||||
@@ -217,7 +218,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# Download runner package tar.gz/zip produced by 'build' job
|
||||
- name: Download Artifact
|
||||
@@ -239,27 +240,43 @@ jobs:
|
||||
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(/<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(/<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(/<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(/<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}}')
|
||||
releaseNote = releaseNote.replace(/<LINUX_ARM_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.linux-arm-sha-noruntime-noexternals}}')
|
||||
releaseNote = releaseNote.replace(/<LINUX_ARM64_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.linux-arm64-sha-noruntime-noexternals}}')
|
||||
console.log(releaseNote)
|
||||
core.setOutput('version', runnerVersion);
|
||||
core.setOutput('note', releaseNote);
|
||||
|
||||
- name: Validate Packages HASH
|
||||
working-directory: _package
|
||||
run: |
|
||||
ls -l
|
||||
echo "${{needs.build.outputs.win-x64-sha}} actions-runner-win-x64-${{ steps.releaseNote.outputs.version }}.zip" | shasum -a 256 -c
|
||||
echo "${{needs.build.outputs.osx-x64-sha}} actions-runner-osx-x64-${{ steps.releaseNote.outputs.version }}.tar.gz" | shasum -a 256 -c
|
||||
echo "${{needs.build.outputs.osx-arm64-sha}} actions-runner-osx-arm64-${{ steps.releaseNote.outputs.version }}.tar.gz" | shasum -a 256 -c
|
||||
echo "${{needs.build.outputs.linux-x64-sha}} actions-runner-linux-x64-${{ steps.releaseNote.outputs.version }}.tar.gz" | shasum -a 256 -c
|
||||
echo "${{needs.build.outputs.linux-arm-sha}} actions-runner-linux-arm-${{ steps.releaseNote.outputs.version }}.tar.gz" | shasum -a 256 -c
|
||||
echo "${{needs.build.outputs.linux-arm64-sha}} actions-runner-linux-arm64-${{ steps.releaseNote.outputs.version }}.tar.gz" | shasum -a 256 -c
|
||||
|
||||
# Create GitHub release
|
||||
- uses: actions/create-release@master
|
||||
id: createRelease
|
||||
@@ -303,6 +320,16 @@ jobs:
|
||||
asset_name: actions-runner-osx-x64-${{ steps.releaseNote.outputs.version }}.tar.gz
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload Release Asset (osx-arm64)
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.createRelease.outputs.upload_url }}
|
||||
asset_path: ${{ github.workspace }}/_package/actions-runner-osx-arm64-${{ steps.releaseNote.outputs.version }}.tar.gz
|
||||
asset_name: actions-runner-osx-arm64-${{ steps.releaseNote.outputs.version }}.tar.gz
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload Release Asset (linux-arm)
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
env:
|
||||
@@ -354,6 +381,16 @@ jobs:
|
||||
asset_name: actions-runner-osx-x64-${{ steps.releaseNote.outputs.version }}-noexternals.tar.gz
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload Release Asset (osx-arm64-noexternals)
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.createRelease.outputs.upload_url }}
|
||||
asset_path: ${{ github.workspace }}/_package_trims/trim_externals/actions-runner-osx-arm64-${{ steps.releaseNote.outputs.version }}-noexternals.tar.gz
|
||||
asset_name: actions-runner-osx-arm64-${{ steps.releaseNote.outputs.version }}-noexternals.tar.gz
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload Release Asset (linux-arm-noexternals)
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
env:
|
||||
@@ -405,6 +442,16 @@ jobs:
|
||||
asset_name: actions-runner-osx-x64-${{ steps.releaseNote.outputs.version }}-noruntime.tar.gz
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload Release Asset (osx-arm64-noruntime)
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.createRelease.outputs.upload_url }}
|
||||
asset_path: ${{ github.workspace }}/_package_trims/trim_runtime/actions-runner-osx-arm64-${{ steps.releaseNote.outputs.version }}-noruntime.tar.gz
|
||||
asset_name: actions-runner-osx-arm64-${{ steps.releaseNote.outputs.version }}-noruntime.tar.gz
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload Release Asset (linux-arm-noruntime)
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
env:
|
||||
@@ -456,6 +503,16 @@ jobs:
|
||||
asset_name: actions-runner-osx-x64-${{ steps.releaseNote.outputs.version }}-noruntime-noexternals.tar.gz
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload Release Asset (osx-arm64-noruntime-noexternals)
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.createRelease.outputs.upload_url }}
|
||||
asset_path: ${{ github.workspace }}/_package_trims/trim_runtime_externals/actions-runner-osx-arm64-${{ steps.releaseNote.outputs.version }}-noruntime-noexternals.tar.gz
|
||||
asset_name: actions-runner-osx-arm64-${{ steps.releaseNote.outputs.version }}-noruntime-noexternals.tar.gz
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload Release Asset (linux-arm-noruntime-noexternals)
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
env:
|
||||
@@ -507,6 +564,16 @@ jobs:
|
||||
asset_name: actions-runner-osx-x64-${{ steps.releaseNote.outputs.version }}-trimmedpackages.json
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload Release Asset (osx-arm64-trimmedpackages.json)
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.createRelease.outputs.upload_url }}
|
||||
asset_path: ${{ github.workspace }}/osx-arm64-trimmedpackages.json
|
||||
asset_name: actions-runner-osx-arm64-${{ steps.releaseNote.outputs.version }}-trimmedpackages.json
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload Release Asset (linux-arm-trimmedpackages.json)
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
env:
|
||||
|
||||
15
.vscode/launch.json
vendored
15
.vscode/launch.json
vendored
@@ -12,8 +12,7 @@
|
||||
],
|
||||
"cwd": "${workspaceFolder}/src",
|
||||
"console": "integratedTerminal",
|
||||
"requireExactSource": false,
|
||||
"targetArchitecture": "x86_64"
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "Run",
|
||||
@@ -25,8 +24,7 @@
|
||||
],
|
||||
"cwd": "${workspaceFolder}/src",
|
||||
"console": "integratedTerminal",
|
||||
"requireExactSource": false,
|
||||
"targetArchitecture": "x86_64"
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "Configure",
|
||||
@@ -39,24 +37,21 @@
|
||||
],
|
||||
"cwd": "${workspaceFolder}/src",
|
||||
"console": "integratedTerminal",
|
||||
"requireExactSource": false,
|
||||
"targetArchitecture": "x86_64"
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "Debug Worker",
|
||||
"type": "coreclr",
|
||||
"request": "attach",
|
||||
"processName": "Runner.Worker",
|
||||
"requireExactSource": false,
|
||||
"targetArchitecture": "x86_64"
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "Attach Debugger",
|
||||
"type": "coreclr",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}",
|
||||
"requireExactSource": false,
|
||||
"targetArchitecture": "x86_64"
|
||||
"requireExactSource": false
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
83
docs/adrs/1751-runner-job-hooks.md
Normal file
83
docs/adrs/1751-runner-job-hooks.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# ADR: Notification Hooks for Runners
|
||||
|
||||
## Context
|
||||
|
||||
This ADR details the design changes for supporting custom configurable hooks for on various runner events. This has been a long requested user feature [here](https://github.com/actions/runner/issues/1543), [here](https://github.com/actions/runner/issues/699) and [here](https://github.com/actions/runner/issues/1116) for users to have more information on runner observability, and for the ability to run cleanup and teardown jobs.
|
||||
|
||||
This feature is mainly intended for self hosted runner administrators.
|
||||
|
||||
**What we hope to solve with this feature**
|
||||
1. A runner admininstrator is able to add custom scripts to cleanup their runner environment at the start or end of a job
|
||||
2. A runner admininstrator is able to add custom scripts to help setup their runner environment at the beginning of a job, for reasons like [caching](https://github.com/actions/runner/issues/1543#issuecomment-1050346279)
|
||||
3. A runner administrator is able to grab custom telemetry of jobs running on their self hosted runner
|
||||
|
||||
**What we don't think this will solve**
|
||||
- Policy features that require certain steps run at the beginning or end of all jobs
|
||||
- This would be better solved to in a central place in settings, rather then decentralized on each runner.
|
||||
- The Proposed `Notification Hooks for Runners` is limited to self hosted runners, we don't beileve Policy features should be
|
||||
- Reuse scenarios between jobs are covered by [composite actions](https://docs.github.com/en/actions/creating-actions/creating-a-composite-action) and [resuable workflows](https://docs.github.com/en/actions/using-workflows/reusing-workflows)
|
||||
- Security applications, security should be handled on the policy side on the server, not decentralized on each runner
|
||||
|
||||
## Hooks
|
||||
- We will expose 2 variables that users can set to enable hooks
|
||||
- `ACTIONS_RUNNER_HOOK_JOB_STARTED`
|
||||
- `ACTIONS_RUNNER_HOOK_JOB_COMPLETED`
|
||||
|
||||
You can set these variables to the **absolute** path of a a `.sh` or `.ps1` file.
|
||||
|
||||
We will execute `pwsh` (fallback to `powershell`) or `bash` (fallback to `sh`) as appropriate.
|
||||
- `.sh` files will execute with the args `-e {pathtofile}`
|
||||
- `.ps1` files will execute with the args `-command \". '{pathtofile}'\"`
|
||||
|
||||
We will **not** set the [standard flags we typically set](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsshell) for `runs` commands. So, if you want to set `pipefail` on `bash` for example, you will need to do that in your script.
|
||||
|
||||
### UI
|
||||
We want to ensure the experience for users invoking workflows is good, if hooks take too long, you may feel your job is delayed or broken. So, much like `Set Up Job`, we will generate two new steps automatically in your job, one for each configured hook:
|
||||
- `Set up runner`
|
||||
- `Complete runner`
|
||||
|
||||
These steps will contain all of the output from invoking your hook, so you will have visibility into the runtime. We will also provide information on the path to the hook, and what shell we are invoking it as, much like we do for `run: ` steps.
|
||||
|
||||
### Contexts
|
||||
When running your hooks, some context on your job may be helpful.
|
||||
- The scripts will have access to the standard [default environment variables](https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables)
|
||||
- Some of these variables are step specific like `GITHUB_ACTION`, in which case they will not be set
|
||||
- You can pull the full webhook event payload from `GITHUB_EVENT_PATH`
|
||||
|
||||
### Commands
|
||||
Should we expose [Commands](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions) and [Environment Files](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#environment-files)
|
||||
|
||||
**Yes**. Imagine a scenario where a runner administrator is deprecating a runner pool, and they need to [warn users](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-a-warning-message) to swap to a different pool, we should support them in doing this. However, there are some limitations:
|
||||
- [save-state](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#sending-values-to-the-pre-and-post-actions) will **not** be supported, these are not traditional steps with pre and post actions
|
||||
- [set-output](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#using-workflow-commands-to-access-toolkit-functions) will **not** be supported, there is no `id` as this is not a traditional step
|
||||
|
||||
|
||||
### Environment Files
|
||||
We will also enable [Environment Files](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#environment-files) to support setup scenarios for the runner environment.
|
||||
|
||||
While a self hosted runner admin can [set env variables](https://docs.github.com/en/actions/hosting-your-own-runners/using-a-proxy-server-with-self-hosted-runners#using-a-env-file-to-set-the-proxy-configuration), these apply to all jobs. By enabling the ability to `add a path` and `set an env` we give runner admins the ability to do this dynamically based on the [workflows environment variables](https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables) to empower setup scenarios.
|
||||
|
||||
|
||||
### Exit codes
|
||||
These are **synchronous** hooks, so they will block job execution while they are being run. Exit code 0 will indicate a successful run of the hook and we will proceed with the job, any other exit code will fail the job with an appropriate annotation.
|
||||
- There will be no support for `continue-on-error`
|
||||
|
||||
## Key Decisions
|
||||
- We will expose 2 variables that users can set to enable hooks
|
||||
- `ACTIONS_RUNNER_HOOK_JOB_STARTED`
|
||||
- `ACTIONS_RUNNER_HOOK_JOB_COMPLETED`
|
||||
- Users can set these variables to the path of a `.sh` or `.ps1` file, which we will execute when Jobs are started or completed.
|
||||
- Output from these will be added to a new step at the start/end of a job named `Set up runner` or `Complete runner`.
|
||||
- These steps will only be generated on runs with these hooks
|
||||
- These hooks `always()` execute if the env variable is set
|
||||
- These files will execute as the Runner user, outside of any container specification on the job
|
||||
- These are **synchronous** hooks
|
||||
- Runner admins can execute a background process for async hooks if they want
|
||||
- We will fail the job and halt execution on any exit code that is not 0. The Runner admin is responsible for returning the correct exit code and ensuring resilency.
|
||||
- This includes that the runner user needs access to the file in the env and the file must exist
|
||||
- There will be no `continue-on-error` type option on launch
|
||||
- There will be no `timeout` option on launch
|
||||
|
||||
## Consequences
|
||||
- Runner admins have the ability to tie into the runner job execution to publish their own telemetry or perform their own cleanup or setup
|
||||
- New steps will be added to the UI showcasing the output of these hooks
|
||||
@@ -5,12 +5,6 @@
|
||||
## Supported Versions
|
||||
|
||||
- macOS High Sierra (10.13) and later versions
|
||||
|
||||
## Apple Silicon M1
|
||||
|
||||
The runner is currently not supported on devices with an Apple M1 chip.
|
||||
We are waiting for official .NET support. You can read more here about the [current state of support here](https://github.com/orgs/dotnet/projects/18#card-56812463).
|
||||
Current .NET project board about M1 support:
|
||||
https://github.com/orgs/dotnet/projects/18#card-56812463
|
||||
- x64 and arm64 (Apple Silicon)
|
||||
|
||||
## [More .Net Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/macos-prerequisites?tabs=netcore30)
|
||||
|
||||
@@ -1,33 +1,12 @@
|
||||
## Features
|
||||
|
||||
- Make run.sh|cmd handle update without quitting so containers using them as entrypoints don't exit on update (#1646, #1633, #1708)
|
||||
- Add support for Step Summary (#1642, #1667, #1712)
|
||||
- Pass jobId to the actionsDownloadInfo controller (#1639)
|
||||
- updated systemd svc.sh to accept custom service file (#1612)
|
||||
- Add ability to specify runner group when creating service (#1675)
|
||||
- Prefer node16 over node12 when running internal scripts (#1621)
|
||||
- Sending telemetry about actions usage. (#1688)
|
||||
- Bump node12 version to latest (#1651)
|
||||
- Add internal to node version function and use better env var name (#1715)
|
||||
- Force JS Actions Node version to 16 if FF is on unless user opted out (#1716)
|
||||
- Added a pre-release package for the `macOS-arm64` architecture
|
||||
- Note that this packages is pre-release status and may not work with all existing actions
|
||||
|
||||
## Bugs
|
||||
- Fix windows console runner update crash (#1670)
|
||||
- Retry policy for methods GetTenantCredential and GetJITRunnerTokenAsync (#1691)
|
||||
- Skip DeleteAgentSession when the acess token has been revoked. (#1692)
|
||||
- Repaired hashFiles call so if error was thrown, it was returned to process invoker (#1678)
|
||||
- Runner throws null ref exception when new line after EOF is missing (#1687)
|
||||
- Lets allow up to 150 characters for services on linux/mac (#1710)
|
||||
- Fixed an issue where live console logs would fail to close (#1903)
|
||||
|
||||
## Misc
|
||||
|
||||
- Added examples and aligned language within docs/checks/actions.md (#1664)
|
||||
- Problem with debugging on macOS M1 (#1625)
|
||||
- Fix typo in hashFiles.ts. (#1672)
|
||||
- Allow mocked updates for E2E testing (#1654)
|
||||
- Move JobTelemetry and StepsTelemetry into GlobalContext. (#1680)
|
||||
- Fix inconsistency of outputs (both canceled and cancelled are used (#1624)
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -42,7 +21,7 @@ Add-Type -AssemblyName System.IO.Compression.FileSystem ;
|
||||
[System.IO.Compression.ZipFile]::ExtractToDirectory("$PWD\actions-runner-win-x64-<RUNNER_VERSION>.zip", "$PWD")
|
||||
```
|
||||
|
||||
## OSX
|
||||
## OSX x64
|
||||
|
||||
``` bash
|
||||
# Create a folder
|
||||
@@ -53,6 +32,17 @@ curl -O -L https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>
|
||||
tar xzf ./actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz
|
||||
```
|
||||
|
||||
## [Pre-release] OSX arm64 (Apple silicon)
|
||||
|
||||
``` bash
|
||||
# Create a folder
|
||||
mkdir actions-runner && cd actions-runner
|
||||
# Download the latest runner package
|
||||
curl -O -L https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-osx-arm64-<RUNNER_VERSION>.tar.gz
|
||||
# Extract the installer
|
||||
tar xzf ./actions-runner-osx-arm64-<RUNNER_VERSION>.tar.gz
|
||||
```
|
||||
|
||||
## Linux x64
|
||||
|
||||
``` bash
|
||||
@@ -95,24 +85,28 @@ The SHA-256 checksums for the packages included in this build are shown below:
|
||||
|
||||
- actions-runner-win-x64-<RUNNER_VERSION>.zip <!-- BEGIN SHA win-x64 --><WIN_X64_SHA><!-- END SHA win-x64 -->
|
||||
- actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz <!-- BEGIN SHA osx-x64 --><OSX_X64_SHA><!-- END SHA osx-x64 -->
|
||||
- actions-runner-osx-arm64-<RUNNER_VERSION>.tar.gz <!-- BEGIN SHA osx-arm64 --><OSX_ARM64_SHA><!-- END SHA osx-arm64 -->
|
||||
- actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz <!-- BEGIN SHA linux-x64 --><LINUX_X64_SHA><!-- END SHA linux-x64 -->
|
||||
- actions-runner-linux-arm64-<RUNNER_VERSION>.tar.gz <!-- BEGIN SHA linux-arm64 --><LINUX_ARM64_SHA><!-- END SHA linux-arm64 -->
|
||||
- actions-runner-linux-arm-<RUNNER_VERSION>.tar.gz <!-- BEGIN SHA linux-arm --><LINUX_ARM_SHA><!-- END SHA linux-arm -->
|
||||
|
||||
- actions-runner-win-x64-<RUNNER_VERSION>-noexternals.zip <!-- BEGIN SHA win-x64_noexternals --><WIN_X64_SHA_NOEXTERNALS><!-- END SHA win-x64_noexternals -->
|
||||
- actions-runner-osx-x64-<RUNNER_VERSION>-noexternals.tar.gz <!-- BEGIN SHA osx-x64_noexternals --><OSX_X64_SHA_NOEXTERNALS><!-- END SHA osx-x64_noexternals -->
|
||||
- actions-runner-osx-arm64-<RUNNER_VERSION>-noexternals.tar.gz <!-- BEGIN SHA osx-arm64_noexternals --><OSX_ARM64_SHA_NOEXTERNALS><!-- END SHA osx-arm64_noexternals -->
|
||||
- actions-runner-linux-x64-<RUNNER_VERSION>-noexternals.tar.gz <!-- BEGIN SHA linux-x64_noexternals --><LINUX_X64_SHA_NOEXTERNALS><!-- END SHA linux-x64_noexternals -->
|
||||
- actions-runner-linux-arm64-<RUNNER_VERSION>-noexternals.tar.gz <!-- BEGIN SHA linux-arm64_noexternals --><LINUX_ARM64_SHA_NOEXTERNALS><!-- END SHA linux-arm64_noexternals -->
|
||||
- actions-runner-linux-arm-<RUNNER_VERSION>-noexternals.tar.gz <!-- BEGIN SHA linux-arm_noexternals --><LINUX_ARM_SHA_NOEXTERNALS><!-- END SHA linux-arm_noexternals -->
|
||||
|
||||
- actions-runner-win-x64-<RUNNER_VERSION>-noruntime.zip <!-- BEGIN SHA win-x64_noruntime --><WIN_X64_SHA_NORUNTIME><!-- END SHA win-x64_noruntime -->
|
||||
- actions-runner-osx-x64-<RUNNER_VERSION>-noruntime.tar.gz <!-- BEGIN SHA osx-x64_noruntime --><OSX_X64_SHA_NORUNTIME><!-- END SHA osx-x64_noruntime -->
|
||||
- actions-runner-osx-arm64-<RUNNER_VERSION>-noruntime.tar.gz <!-- BEGIN SHA osx-arm64_noruntime --><OSX_ARM64_SHA_NORUNTIME><!-- END SHA osx-arm64_noruntime -->
|
||||
- actions-runner-linux-x64-<RUNNER_VERSION>-noruntime.tar.gz <!-- BEGIN SHA linux-x64_noruntime --><LINUX_X64_SHA_NORUNTIME><!-- END SHA linux-x64_noruntime -->
|
||||
- actions-runner-linux-arm64-<RUNNER_VERSION>-noruntime.tar.gz <!-- BEGIN SHA linux-arm64_noruntime --><LINUX_ARM64_SHA_NORUNTIME><!-- END SHA linux-arm64_noruntime -->
|
||||
- actions-runner-linux-arm-<RUNNER_VERSION>-noruntime.tar.gz <!-- BEGIN SHA linux-arm_noruntime --><LINUX_ARM_SHA_NORUNTIME><!-- END SHA linux-arm_noruntime -->
|
||||
|
||||
- actions-runner-win-x64-<RUNNER_VERSION>-noruntime-noexternals.zip <!-- BEGIN SHA win-x64_noruntime_noexternals --><WIN_X64_SHA_NORUNTIME_NOEXTERNALS><!-- END SHA win-x64_noruntime_noexternals -->
|
||||
- actions-runner-osx-x64-<RUNNER_VERSION>-noruntime-noexternals.tar.gz <!-- BEGIN SHA osx-x64_noruntime_noexternals --><OSX_X64_SHA_NORUNTIME_NOEXTERNALS><!-- END SHA osx-x64_noruntime_noexternals -->
|
||||
- actions-runner-osx-arm64-<RUNNER_VERSION>-noruntime-noexternals.tar.gz <!-- BEGIN SHA osx-arm64_noruntime_noexternals --><OSX_ARM64_SHA_NORUNTIME_NOEXTERNALS><!-- END SHA osx-arm64_noruntime_noexternals -->
|
||||
- actions-runner-linux-x64-<RUNNER_VERSION>-noruntime-noexternals.tar.gz <!-- BEGIN SHA linux-x64_noruntime_noexternals --><LINUX_X64_SHA_NORUNTIME_NOEXTERNALS><!-- END SHA linux-x64_noruntime_noexternals -->
|
||||
- actions-runner-linux-arm64-<RUNNER_VERSION>-noruntime-noexternals.tar.gz <!-- BEGIN SHA linux-arm64_noruntime_noexternals --><LINUX_ARM64_SHA_NORUNTIME_NOEXTERNALS><!-- END SHA linux-arm64_noruntime_noexternals -->
|
||||
- actions-runner-linux-arm-<RUNNER_VERSION>-noruntime-noexternals.tar.gz <!-- BEGIN SHA linux-arm_noruntime_noexternals --><LINUX_ARM_SHA_NORUNTIME_NOEXTERNALS><!-- END SHA linux-arm_noruntime_noexternals -->
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.288.0
|
||||
<Update to ./src/runnerversion when creating release>
|
||||
|
||||
@@ -25,9 +25,12 @@
|
||||
<DefineConstants>$(DefineConstants);X86</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(BUILD_OS)' == 'OSX'">
|
||||
<PropertyGroup Condition="'$(BUILD_OS)' == 'OSX' AND '$(PackageRuntime)' == 'osx-x64'">
|
||||
<DefineConstants>$(DefineConstants);X64</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(BUILD_OS)' == 'OSX' AND '$(PackageRuntime)' == 'osx-arm64'">
|
||||
<DefineConstants>$(DefineConstants);ARM64</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(BUILD_OS)' == 'Linux' AND ('$(PackageRuntime)' == 'linux-x64' OR '$(PackageRuntime)' == '')">
|
||||
<DefineConstants>$(DefineConstants);X64</DefineConstants>
|
||||
|
||||
@@ -1 +1 @@
|
||||
de62d296708908cfd1236e58869aebbc2bae8a8c3d629276968542626c508e37
|
||||
1d709d93e5d3c6c6c656a61aa6c1781050224788a05b0e6ecc4c3c0408bdf89c
|
||||
@@ -1 +1 @@
|
||||
44fcd0422dd98ed17d2c8e9057ff2260c50165f20674236a4ae7d2645a07df25
|
||||
b92a47cfeaad02255b1f7a377060651b73ae5e5db22a188dbbcb4183ab03a03d
|
||||
@@ -1 +1 @@
|
||||
e57652cf322ee16ce3af4f9e58f80858746b9e1e60279e991a3b3d9a6baf8d79
|
||||
68a9a8ef0843a8bb74241894f6f63fd76241a82295c5337d3cc7a940a314c78e
|
||||
1
src/Misc/contentHash/dotnetRuntime/osx-arm64
Normal file
1
src/Misc/contentHash/dotnetRuntime/osx-arm64
Normal file
@@ -0,0 +1 @@
|
||||
02c7126ff4d63ee2a0ae390c81434c125630522aadf35903bbeebb1a99d8af99
|
||||
@@ -1 +1 @@
|
||||
bdd247b2ff3f51095524412e2ac588e7a87af805e114d6caf2368366ee7be1ea
|
||||
c9d5a542f8d765168855a89e83ae0a8970d00869041c4f9a766651c04c72b212
|
||||
@@ -1 +1 @@
|
||||
d23a0cb9f20c0aa1cddb7a39567cd097020cdeb06a1e952940601d1a405c53b8
|
||||
d94f2fbaf210297162bc9f3add819d73682c3aa6899e321c3872412b924d5504
|
||||
1
src/Misc/contentHash/externals/osx-arm64
vendored
Normal file
1
src/Misc/contentHash/externals/osx-arm64
vendored
Normal file
@@ -0,0 +1 @@
|
||||
cc4708962a80325de0baa5ae8484e0cb9ae976ac6a4178c1c0d448b8c52bd7f7
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"plugins": ["jest", "@typescript-eslint"],
|
||||
"extends": ["plugin:github/es6"],
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"extends": ["plugin:github/recommended"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 9,
|
||||
@@ -17,13 +17,16 @@
|
||||
"@typescript-eslint/no-require-imports": "error",
|
||||
"@typescript-eslint/array-type": "error",
|
||||
"@typescript-eslint/await-thenable": "error",
|
||||
"@typescript-eslint/ban-ts-ignore": "error",
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"error",
|
||||
{
|
||||
"selector": "default",
|
||||
"format": ["camelCase"]
|
||||
}
|
||||
],
|
||||
"camelcase": "off",
|
||||
"@typescript-eslint/camelcase": "error",
|
||||
"@typescript-eslint/class-name-casing": "error",
|
||||
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
|
||||
"@typescript-eslint/func-call-spacing": ["error", "never"],
|
||||
"@typescript-eslint/generic-type-naming": ["error", "^[A-Z][A-Za-z]*$"],
|
||||
"@typescript-eslint/no-array-constructor": "error",
|
||||
"@typescript-eslint/no-empty-interface": "error",
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
@@ -33,7 +36,6 @@
|
||||
"@typescript-eslint/no-misused-new": "error",
|
||||
"@typescript-eslint/no-namespace": "error",
|
||||
"@typescript-eslint/no-non-null-assertion": "warn",
|
||||
"@typescript-eslint/no-object-literal-type-assertion": "error",
|
||||
"@typescript-eslint/no-unnecessary-qualifier": "error",
|
||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||
"@typescript-eslint/no-useless-constructor": "error",
|
||||
@@ -41,19 +43,19 @@
|
||||
"@typescript-eslint/prefer-for-of": "warn",
|
||||
"@typescript-eslint/prefer-function-type": "warn",
|
||||
"@typescript-eslint/prefer-includes": "error",
|
||||
"@typescript-eslint/prefer-interface": "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",
|
||||
"semi": "off",
|
||||
"@typescript-eslint/semi": ["error", "never"],
|
||||
"@typescript-eslint/type-annotation-spacing": "error",
|
||||
"@typescript-eslint/unbound-method": "error"
|
||||
"@typescript-eslint/unbound-method": "error",
|
||||
"filenames/match-regex" : "off",
|
||||
"github/no-then" : 1, // warning
|
||||
"semi": "off"
|
||||
},
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true,
|
||||
"jest/globals": true
|
||||
"es6": true
|
||||
}
|
||||
}
|
||||
6073
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
6073
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -25,10 +25,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^12.7.12",
|
||||
"@typescript-eslint/parser": "^2.8.0",
|
||||
"@typescript-eslint/parser": "^5.15.0",
|
||||
"@zeit/ncc": "^0.20.5",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-plugin-github": "^2.0.0",
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-plugin-github": "^4.3.5",
|
||||
"prettier": "^1.19.1",
|
||||
"typescript": "^3.6.4"
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as glob from '@actions/glob'
|
||||
import * as crypto from 'crypto'
|
||||
import * as fs from 'fs'
|
||||
import * as glob from '@actions/glob'
|
||||
import * as path from 'path'
|
||||
import * as stream from 'stream'
|
||||
import * as util from 'util'
|
||||
import * as path from 'path'
|
||||
|
||||
async function run(): Promise<void> {
|
||||
// arg0 -> node
|
||||
|
||||
@@ -140,6 +140,11 @@ if [[ "$PACKAGERUNTIME" == "osx-x64" ]]; then
|
||||
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-darwin-x64.tar.gz" node16 fix_nested_dir
|
||||
fi
|
||||
|
||||
if [[ "$PACKAGERUNTIME" == "osx-arm64" ]]; then
|
||||
# node.js v12 doesn't support macOS on arm64.
|
||||
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-darwin-arm64.tar.gz" node16 fix_nested_dir
|
||||
fi
|
||||
|
||||
# Download the external tools for Linux PACKAGERUNTIMEs.
|
||||
if [[ "$PACKAGERUNTIME" == "linux-x64" ]]; then
|
||||
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/node-v${NODE12_VERSION}-linux-x64.tar.gz" node12 fix_nested_dir
|
||||
|
||||
@@ -3,94 +3,135 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
var childProcess = require("child_process");
|
||||
var path = require("path")
|
||||
var path = require("path");
|
||||
|
||||
var supported = ['linux', 'darwin']
|
||||
var supported = ["linux", "darwin"];
|
||||
|
||||
if (supported.indexOf(process.platform) == -1) {
|
||||
console.log('Unsupported platform: ' + process.platform);
|
||||
console.log('Supported platforms are: ' + supported.toString());
|
||||
process.exit(1);
|
||||
console.log("Unsupported platform: " + process.platform);
|
||||
console.log("Supported platforms are: " + supported.toString());
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var stopping = false;
|
||||
var listener = null;
|
||||
|
||||
var runService = function () {
|
||||
var listenerExePath = path.join(__dirname, '../bin/Runner.Listener');
|
||||
var interactive = process.argv[2] === "interactive";
|
||||
var exitServiceAfterNFailures = Number(
|
||||
process.env.GITHUB_ACTIONS_SERVICE_EXIT_AFTER_N_FAILURES
|
||||
);
|
||||
|
||||
if (!stopping) {
|
||||
try {
|
||||
if (interactive) {
|
||||
console.log('Starting Runner listener interactively');
|
||||
listener = childProcess.spawn(listenerExePath, ['run'], { env: process.env });
|
||||
} else {
|
||||
console.log('Starting Runner listener with startup type: service');
|
||||
listener = childProcess.spawn(listenerExePath, ['run', '--startuptype', 'service'], { env: process.env });
|
||||
}
|
||||
|
||||
console.log(`Started listener process, pid: ${listener.pid}`);
|
||||
|
||||
listener.stdout.on('data', (data) => {
|
||||
process.stdout.write(data.toString('utf8'));
|
||||
});
|
||||
|
||||
listener.stderr.on('data', (data) => {
|
||||
process.stdout.write(data.toString('utf8'));
|
||||
});
|
||||
|
||||
listener.on("error", (err) => {
|
||||
console.log(`Runner listener fail to start with error ${err.message}`);
|
||||
});
|
||||
|
||||
listener.on('close', (code) => {
|
||||
console.log(`Runner listener exited with error code ${code}`);
|
||||
|
||||
if (code === 0) {
|
||||
console.log('Runner listener exit with 0 return code, stop the service, no retry needed.');
|
||||
stopping = true;
|
||||
} else if (code === 1) {
|
||||
console.log('Runner listener exit with terminated error, stop the service, no retry needed.');
|
||||
stopping = true;
|
||||
} else if (code === 2) {
|
||||
console.log('Runner listener exit with retryable error, re-launch runner in 5 seconds.');
|
||||
} else if (code === 3) {
|
||||
console.log('Runner listener exit because of updating, re-launch runner in 5 seconds.');
|
||||
} else {
|
||||
console.log('Runner listener exit with undefined return code, re-launch runner in 5 seconds.');
|
||||
}
|
||||
|
||||
if (!stopping) {
|
||||
setTimeout(runService, 5000);
|
||||
}
|
||||
});
|
||||
|
||||
} catch (ex) {
|
||||
console.log(ex);
|
||||
}
|
||||
}
|
||||
if (exitServiceAfterNFailures <= 0) {
|
||||
exitServiceAfterNFailures = NaN;
|
||||
}
|
||||
|
||||
var consecutiveFailureCount = 0;
|
||||
|
||||
var gracefulShutdown = function () {
|
||||
console.log("Shutting down runner listener");
|
||||
stopping = true;
|
||||
if (listener) {
|
||||
console.log("Sending SIGINT to runner listener to stop");
|
||||
listener.kill("SIGINT");
|
||||
|
||||
console.log("Sending SIGKILL to runner listener");
|
||||
setTimeout(() => listener.kill("SIGKILL"), 30000).unref();
|
||||
}
|
||||
};
|
||||
|
||||
var runService = function () {
|
||||
var listenerExePath = path.join(__dirname, "../bin/Runner.Listener");
|
||||
var interactive = process.argv[2] === "interactive";
|
||||
|
||||
if (!stopping) {
|
||||
try {
|
||||
if (interactive) {
|
||||
console.log("Starting Runner listener interactively");
|
||||
listener = childProcess.spawn(listenerExePath, ["run"], {
|
||||
env: process.env,
|
||||
});
|
||||
} else {
|
||||
console.log("Starting Runner listener with startup type: service");
|
||||
listener = childProcess.spawn(
|
||||
listenerExePath,
|
||||
["run", "--startuptype", "service"],
|
||||
{ env: process.env }
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`Started listener process, pid: ${listener.pid}`);
|
||||
|
||||
listener.stdout.on("data", (data) => {
|
||||
if (data.toString("utf8").includes("Listening for Jobs")) {
|
||||
consecutiveFailureCount = 0;
|
||||
}
|
||||
process.stdout.write(data.toString("utf8"));
|
||||
});
|
||||
|
||||
listener.stderr.on("data", (data) => {
|
||||
process.stdout.write(data.toString("utf8"));
|
||||
});
|
||||
|
||||
listener.on("error", (err) => {
|
||||
console.log(`Runner listener fail to start with error ${err.message}`);
|
||||
});
|
||||
|
||||
listener.on("close", (code) => {
|
||||
console.log(`Runner listener exited with error code ${code}`);
|
||||
|
||||
if (code === 0) {
|
||||
console.log(
|
||||
"Runner listener exit with 0 return code, stop the service, no retry needed."
|
||||
);
|
||||
stopping = true;
|
||||
} else if (code === 1) {
|
||||
console.log(
|
||||
"Runner listener exit with terminated error, stop the service, no retry needed."
|
||||
);
|
||||
stopping = true;
|
||||
} else if (code === 2) {
|
||||
console.log(
|
||||
"Runner listener exit with retryable error, re-launch runner in 5 seconds."
|
||||
);
|
||||
consecutiveFailureCount = 0;
|
||||
} else if (code === 3 || code === 4) {
|
||||
console.log(
|
||||
"Runner listener exit because of updating, re-launch runner in 5 seconds."
|
||||
);
|
||||
consecutiveFailureCount = 0;
|
||||
} else {
|
||||
var messagePrefix = "Runner listener exit with undefined return code";
|
||||
consecutiveFailureCount++;
|
||||
if (
|
||||
!isNaN(exitServiceAfterNFailures) &&
|
||||
consecutiveFailureCount >= exitServiceAfterNFailures
|
||||
) {
|
||||
console.error(
|
||||
`${messagePrefix}, exiting service after ${consecutiveFailureCount} consecutive failures`
|
||||
);
|
||||
gracefulShutdown();
|
||||
return;
|
||||
} else {
|
||||
console.log(`${messagePrefix}, re-launch runner in 5 seconds.`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!stopping) {
|
||||
setTimeout(runService, 5000);
|
||||
}
|
||||
});
|
||||
} catch (ex) {
|
||||
console.log(ex);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
runService();
|
||||
console.log('Started running service');
|
||||
console.log("Started running service");
|
||||
|
||||
var gracefulShutdown = function (code) {
|
||||
console.log('Shutting down runner listener');
|
||||
stopping = true;
|
||||
if (listener) {
|
||||
console.log('Sending SIGINT to runner listener to stop');
|
||||
listener.kill('SIGINT');
|
||||
|
||||
console.log('Sending SIGKILL to runner listener');
|
||||
setTimeout(() => listener.kill('SIGKILL'), 30000).unref();
|
||||
}
|
||||
}
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
gracefulShutdown(0);
|
||||
process.on("SIGINT", () => {
|
||||
gracefulShutdown();
|
||||
});
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
gracefulShutdown(0);
|
||||
process.on("SIGTERM", () => {
|
||||
gracefulShutdown();
|
||||
});
|
||||
|
||||
@@ -63,7 +63,7 @@ function install()
|
||||
mkdir -p "${log_path}" || failed "failed to create ${log_path}"
|
||||
|
||||
echo Creating ${PLIST_PATH}
|
||||
sed "s/{{User}}/${SUDO_USER:-$USER}/g; s/{{SvcName}}/$SVC_NAME/g; s@{{RunnerRoot}}@${RUNNER_ROOT}@g; s@{{UserHome}}@$HOME@g;" "${TEMPLATE_PATH}" > "${TEMP_PATH}" || failed "failed to create replacement temp file"
|
||||
sed "s/{{User}}/${USER:-$SUDO_USER}/g; s/{{SvcName}}/$SVC_NAME/g; s@{{RunnerRoot}}@${RUNNER_ROOT}@g; s@{{UserHome}}@$HOME@g;" "${TEMPLATE_PATH}" > "${TEMP_PATH}" || failed "failed to create replacement temp file"
|
||||
mv "${TEMP_PATH}" "${PLIST_PATH}" || failed "failed to copy plist"
|
||||
|
||||
# Since we started with sudo, runsvc.sh will be owned by root. Change this to current login user.
|
||||
|
||||
@@ -1557,12 +1557,12 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const glob = __importStar(__webpack_require__(281));
|
||||
const crypto = __importStar(__webpack_require__(417));
|
||||
const fs = __importStar(__webpack_require__(747));
|
||||
const glob = __importStar(__webpack_require__(281));
|
||||
const path = __importStar(__webpack_require__(622));
|
||||
const stream = __importStar(__webpack_require__(413));
|
||||
const util = __importStar(__webpack_require__(669));
|
||||
const path = __importStar(__webpack_require__(622));
|
||||
function run() {
|
||||
var e_1, _a;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
|
||||
@@ -30,13 +30,13 @@ date "+[%F %T-%4N] Waiting for $runnerprocessname ($runnerpid) to complete" >> "
|
||||
while [ -e /proc/$runnerpid ]
|
||||
do
|
||||
date "+[%F %T-%4N] Process $runnerpid still running" >> "$logfile" 2>&1
|
||||
sleep 2
|
||||
"$rootfolder"/safe_sleep.sh 2
|
||||
done
|
||||
date "+[%F %T-%4N] Process $runnerpid finished running" >> "$logfile" 2>&1
|
||||
|
||||
# start re-organize folders
|
||||
date "+[%F %T-%4N] Sleep 1 more second to make sure process exited" >> "$logfile" 2>&1
|
||||
sleep 1
|
||||
"$rootfolder"/safe_sleep.sh 1
|
||||
|
||||
# the folder structure under runner root will be
|
||||
# ./bin -> bin.2.100.0 (junction folder)
|
||||
|
||||
@@ -10,22 +10,6 @@ fi
|
||||
# Run
|
||||
shopt -s nocasematch
|
||||
|
||||
safe_sleep() {
|
||||
if [ ! -x "$(command -v sleep)" ]; then
|
||||
if [ ! -x "$(command -v ping)" ]; then
|
||||
COUNT="0"
|
||||
while [[ $COUNT != 5000 ]]; do
|
||||
echo "SLEEP" > /dev/null
|
||||
COUNT=$[$COUNT+1]
|
||||
done
|
||||
else
|
||||
ping -c 5 127.0.0.1 > /dev/null
|
||||
fi
|
||||
else
|
||||
sleep 5
|
||||
fi
|
||||
}
|
||||
|
||||
SOURCE="${BASH_SOURCE[0]}"
|
||||
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
@@ -44,17 +28,17 @@ elif [[ $returnCode == 1 ]]; then
|
||||
exit 0
|
||||
elif [[ $returnCode == 2 ]]; then
|
||||
echo "Runner listener exit with retryable error, re-launch runner in 5 seconds."
|
||||
safe_sleep
|
||||
"$DIR"/safe_sleep.sh 5
|
||||
exit 2
|
||||
elif [[ $returnCode == 3 ]]; then
|
||||
# Sleep 5 seconds to wait for the runner update process finish
|
||||
echo "Runner listener exit because of updating, re-launch runner in 5 seconds"
|
||||
safe_sleep
|
||||
"$DIR"/safe_sleep.sh 5
|
||||
exit 2
|
||||
elif [[ $returnCode == 4 ]]; then
|
||||
# Sleep 5 seconds to wait for the ephemeral runner update process finish
|
||||
echo "Runner listener exit because of updating, re-launch ephemeral runner in 5 seconds"
|
||||
safe_sleep
|
||||
"$DIR"/safe_sleep.sh 5
|
||||
exit 2
|
||||
else
|
||||
echo "Exiting with unknown error code: ${returnCode}"
|
||||
|
||||
6
src/Misc/layoutroot/safe_sleep.sh
Normal file
6
src/Misc/layoutroot/safe_sleep.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
SECONDS=0
|
||||
while [[ $SECONDS != $1 ]]; do
|
||||
:
|
||||
done
|
||||
@@ -3,6 +3,7 @@ api-ms-win-core-console-l1-2-0.dll
|
||||
api-ms-win-core-datetime-l1-1-0.dll
|
||||
api-ms-win-core-debug-l1-1-0.dll
|
||||
api-ms-win-core-errorhandling-l1-1-0.dll
|
||||
api-ms-win-core-fibers-l1-1-0.dll
|
||||
api-ms-win-core-file-l1-1-0.dll
|
||||
api-ms-win-core-file-l1-2-0.dll
|
||||
api-ms-win-core-file-l2-1-0.dll
|
||||
@@ -70,7 +71,7 @@ Microsoft.VisualBasic.dll
|
||||
Microsoft.Win32.Primitives.dll
|
||||
Microsoft.Win32.Registry.dll
|
||||
mscordaccore.dll
|
||||
mscordaccore_amd64_amd64_6.0.21.52210.dll
|
||||
mscordaccore_amd64_amd64_6.0.522.21309.dll
|
||||
mscordbi.dll
|
||||
mscorlib.dll
|
||||
mscorrc.debug.dll
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace GitHub.Runner.Common
|
||||
public static class CommandLine
|
||||
{
|
||||
//if you are adding a new arg, please make sure you update the
|
||||
//validArgs array as well present in the CommandSettings.cs
|
||||
//validOptions dictionary as well present in the CommandSettings.cs
|
||||
public static class Args
|
||||
{
|
||||
public static readonly string Auth = "auth";
|
||||
@@ -121,7 +121,7 @@ namespace GitHub.Runner.Common
|
||||
}
|
||||
|
||||
//if you are adding a new flag, please make sure you update the
|
||||
//validFlags array as well present in the CommandSettings.cs
|
||||
//validOptions dictionary as well present in the CommandSettings.cs
|
||||
public static class Flags
|
||||
{
|
||||
public static readonly string Check = "check";
|
||||
@@ -149,6 +149,8 @@ namespace GitHub.Runner.Common
|
||||
public static class Features
|
||||
{
|
||||
public static readonly string DiskSpaceWarning = "runner.diskspace.warning";
|
||||
public static readonly string Node12Warning = "DistributedTask.AddWarningToNode12Action";
|
||||
public static readonly string UseContainerPathForTemplate = "DistributedTask.UseContainerPathForTemplate";
|
||||
}
|
||||
|
||||
public static readonly string InternalTelemetryIssueDataKey = "_internal_telemetry";
|
||||
@@ -158,6 +160,7 @@ namespace GitHub.Runner.Common
|
||||
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. Please update the following actions to use Node.js 16: {0}";
|
||||
}
|
||||
|
||||
public static class RunnerEvent
|
||||
@@ -189,6 +192,12 @@ namespace GitHub.Runner.Common
|
||||
public static readonly string Success = "success";
|
||||
}
|
||||
|
||||
public static class Hooks
|
||||
{
|
||||
public static readonly string JobStartedStepName = "Set up runner";
|
||||
public static readonly string JobCompletedStepName = "Complete runner";
|
||||
}
|
||||
|
||||
public static class Path
|
||||
{
|
||||
public static readonly string ActionsDirectory = "_actions";
|
||||
@@ -218,6 +227,7 @@ namespace GitHub.Runner.Common
|
||||
//
|
||||
public static readonly string AllowUnsupportedCommands = "ACTIONS_ALLOW_UNSECURE_COMMANDS";
|
||||
public static readonly string AllowUnsupportedStopCommandTokens = "ACTIONS_ALLOW_UNSECURE_STOPCOMMAND_TOKENS";
|
||||
public static readonly string RequireJobContainer = "ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER";
|
||||
public static readonly string RunnerDebug = "ACTIONS_RUNNER_DEBUG";
|
||||
public static readonly string StepDebug = "ACTIONS_STEP_DEBUG";
|
||||
public static readonly string AllowActionsUseUnsecureNodeVersion = "ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION";
|
||||
|
||||
@@ -1,25 +1,32 @@
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.WebApi;
|
||||
using GitHub.Services.WebApi.Utilities.Internal;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace GitHub.Runner.Common
|
||||
{
|
||||
[ServiceLocator(Default = typeof(JobServer))]
|
||||
public interface IJobServer : IRunnerService
|
||||
public interface IJobServer : IRunnerService, IAsyncDisposable
|
||||
{
|
||||
Task ConnectAsync(VssConnection jobConnection);
|
||||
|
||||
void InitializeWebsocketClient(ServiceEndpoint serviceEndpoint);
|
||||
|
||||
// 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, CancellationToken cancellationToken);
|
||||
Task AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, long startLine, CancellationToken cancellationToken);
|
||||
Task AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, long? startLine, CancellationToken cancellationToken);
|
||||
Task<TaskAttachment> CreateAttachmentAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, String type, String name, Stream uploadStream, CancellationToken cancellationToken);
|
||||
Task<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);
|
||||
@@ -34,6 +41,20 @@ namespace GitHub.Runner.Common
|
||||
private bool _hasConnection;
|
||||
private VssConnection _connection;
|
||||
private TaskHttpClient _taskClient;
|
||||
private ClientWebSocket _websocketClient;
|
||||
|
||||
private ServiceEndpoint _serviceEndpoint;
|
||||
|
||||
private int totalBatchedLinesAttemptedByWebsocket = 0;
|
||||
private int failedAttemptsToPostBatchedLinesByWebsocket = 0;
|
||||
|
||||
|
||||
private static readonly TimeSpan _minDelayForWebsocketReconnect = TimeSpan.FromMilliseconds(100);
|
||||
private static readonly TimeSpan _maxDelayForWebsocketReconnect = TimeSpan.FromMilliseconds(500);
|
||||
private static readonly int _minWebsocketFailurePercentageAllowed = 50;
|
||||
private static readonly int _minWebsocketBatchedLinesCountToConsider = 5;
|
||||
|
||||
private Task _websocketConnectTask;
|
||||
|
||||
public async Task ConnectAsync(VssConnection jobConnection)
|
||||
{
|
||||
@@ -117,6 +138,21 @@ namespace GitHub.Runner.Common
|
||||
}
|
||||
}
|
||||
|
||||
public void InitializeWebsocketClient(ServiceEndpoint serviceEndpoint)
|
||||
{
|
||||
this._serviceEndpoint = serviceEndpoint;
|
||||
InitializeWebsocketClient(TimeSpan.Zero);
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
CloseWebSocket(WebSocketCloseStatus.NormalClosure, CancellationToken.None);
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
private void CheckConnection()
|
||||
{
|
||||
if (!_hasConnection)
|
||||
@@ -125,6 +161,53 @@ namespace GitHub.Runner.Common
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeWebsocketClient(TimeSpan delay)
|
||||
{
|
||||
if (_serviceEndpoint.Authorization != null &&
|
||||
_serviceEndpoint.Authorization.Parameters.TryGetValue(EndpointAuthorizationParameters.AccessToken, out var accessToken) &&
|
||||
!string.IsNullOrEmpty(accessToken))
|
||||
{
|
||||
if (_serviceEndpoint.Data.TryGetValue("FeedStreamUrl", out var feedStreamUrl) && !string.IsNullOrEmpty(feedStreamUrl))
|
||||
{
|
||||
// let's ensure we use the right scheme
|
||||
feedStreamUrl = feedStreamUrl.Replace("https://", "wss://").Replace("http://", "ws://");
|
||||
Trace.Info($"Creating websocket client ..." + feedStreamUrl);
|
||||
this._websocketClient = new ClientWebSocket();
|
||||
this._websocketClient.Options.SetRequestHeader("Authorization", $"Bearer {accessToken}");
|
||||
var userAgentValues = new List<ProductInfoHeaderValue>();
|
||||
userAgentValues.AddRange(UserAgentUtility.GetDefaultRestUserAgent());
|
||||
userAgentValues.AddRange(HostContext.UserAgents);
|
||||
this._websocketClient.Options.SetRequestHeader("User-Agent", string.Join(" ", userAgentValues.Select(x => x.ToString())));
|
||||
|
||||
this._websocketConnectTask = ConnectWebSocketClient(feedStreamUrl, delay);
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace.Info($"No FeedStreamUrl found, so we will use Rest API calls for sending feed data");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace.Info($"No access token from the service endpoint");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConnectWebSocketClient(string feedStreamUrl, TimeSpan delay)
|
||||
{
|
||||
try
|
||||
{
|
||||
Trace.Info($"Attempting to start websocket client with delay {delay}.");
|
||||
await Task.Delay(delay);
|
||||
await this._websocketClient.ConnectAsync(new Uri(feedStreamUrl), default(CancellationToken));
|
||||
Trace.Info($"Successfully started websocket client.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Info("Exception caught during websocket client connect, fallback of HTTP would be used now instead of websocket.");
|
||||
Trace.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
// Feedback: WebConsole, TimelineRecords and Logs
|
||||
//-----------------------------------------------------------------
|
||||
@@ -135,16 +218,86 @@ namespace GitHub.Runner.Common
|
||||
return _taskClient.AppendLogContentAsync(scopeIdentifier, hubName, planId, logId, uploadStream, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, CancellationToken cancellationToken)
|
||||
public async Task AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, long? startLine, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckConnection();
|
||||
return _taskClient.AppendTimelineRecordFeedAsync(scopeIdentifier, hubName, planId, timelineId, timelineRecordId, stepId, lines, cancellationToken: cancellationToken);
|
||||
var pushedLinesViaWebsocket = false;
|
||||
if (_websocketConnectTask != null)
|
||||
{
|
||||
await _websocketConnectTask;
|
||||
}
|
||||
|
||||
// "_websocketClient != null" implies either: We have a successful connection OR we have to attempt sending again and then reconnect
|
||||
// ...in other words, if websocket client is null, we will skip sending to websocket and just use rest api calls to send data
|
||||
if (_websocketClient != null)
|
||||
{
|
||||
var linesWrapper = startLine.HasValue ? new TimelineRecordFeedLinesWrapper(stepId, lines, startLine.Value) : new TimelineRecordFeedLinesWrapper(stepId, lines);
|
||||
var jsonData = StringUtil.ConvertToJson(linesWrapper);
|
||||
try
|
||||
{
|
||||
totalBatchedLinesAttemptedByWebsocket++;
|
||||
var jsonDataBytes = Encoding.UTF8.GetBytes(jsonData);
|
||||
// break the message into chunks of 1024 bytes
|
||||
for (var i = 0; i < jsonDataBytes.Length; i += 1 * 1024)
|
||||
{
|
||||
var lastChunk = i + (1 * 1024) >= jsonDataBytes.Length;
|
||||
var chunk = new ArraySegment<byte>(jsonDataBytes, i, Math.Min(1 * 1024, jsonDataBytes.Length - i));
|
||||
await _websocketClient.SendAsync(chunk, WebSocketMessageType.Text, endOfMessage: lastChunk, cancellationToken);
|
||||
}
|
||||
|
||||
pushedLinesViaWebsocket = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
failedAttemptsToPostBatchedLinesByWebsocket++;
|
||||
Trace.Info($"Caught exception during append web console line to websocket, let's fallback to sending via non-websocket call (total calls: {totalBatchedLinesAttemptedByWebsocket}, failed calls: {failedAttemptsToPostBatchedLinesByWebsocket}, websocket state: {this._websocketClient?.State}).");
|
||||
Trace.Error(ex);
|
||||
if (totalBatchedLinesAttemptedByWebsocket > _minWebsocketBatchedLinesCountToConsider)
|
||||
{
|
||||
// let's consider failure percentage
|
||||
if (failedAttemptsToPostBatchedLinesByWebsocket * 100 / totalBatchedLinesAttemptedByWebsocket > _minWebsocketFailurePercentageAllowed)
|
||||
{
|
||||
Trace.Info($"Exhausted websocket allowed retries, we will not attempt websocket connection for this job to post lines again.");
|
||||
CloseWebSocket(WebSocketCloseStatus.InternalServerError, cancellationToken);
|
||||
|
||||
// By setting it to null, we will ensure that we never try websocket path again for this job
|
||||
_websocketClient = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (_websocketClient != null)
|
||||
{
|
||||
var delay = BackoffTimerHelper.GetRandomBackoff(_minDelayForWebsocketReconnect, _maxDelayForWebsocketReconnect);
|
||||
Trace.Info($"Websocket is not open, let's attempt to connect back again with random backoff {delay} ms (total calls: {totalBatchedLinesAttemptedByWebsocket}, failed calls: {failedAttemptsToPostBatchedLinesByWebsocket}).");
|
||||
InitializeWebsocketClient(delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!pushedLinesViaWebsocket && !cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
if (startLine.HasValue)
|
||||
{
|
||||
await _taskClient.AppendTimelineRecordFeedAsync(scopeIdentifier, hubName, planId, timelineId, timelineRecordId, stepId, lines, startLine.Value, cancellationToken: cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _taskClient.AppendTimelineRecordFeedAsync(scopeIdentifier, hubName, planId, timelineId, timelineRecordId, stepId, lines, cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, long startLine, CancellationToken cancellationToken)
|
||||
private void CloseWebSocket(WebSocketCloseStatus closeStatus, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckConnection();
|
||||
return _taskClient.AppendTimelineRecordFeedAsync(scopeIdentifier, hubName, planId, timelineId, timelineRecordId, stepId, lines, startLine, cancellationToken: cancellationToken);
|
||||
try
|
||||
{
|
||||
_websocketClient?.CloseOutputAsync(closeStatus, "Closing websocket", cancellationToken);
|
||||
}
|
||||
catch (Exception websocketEx)
|
||||
{
|
||||
// In some cases this might be okay since the websocket might be open yet, so just close and don't trace exceptions
|
||||
Trace.Info($"Failed to close websocket gracefully {websocketEx.GetType().Name}");
|
||||
}
|
||||
}
|
||||
|
||||
public Task<TaskAttachment> CreateAttachmentAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, string type, string name, Stream uploadStream, CancellationToken cancellationToken)
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace GitHub.Runner.Common
|
||||
|
||||
// Web console dequeue will start with process queue every 250ms for the first 60*4 times (~60 seconds).
|
||||
// Then the dequeue will happen every 500ms.
|
||||
// In this way, customer still can get instance live console output on job start,
|
||||
// In this way, customer still can get instance live console output on job start,
|
||||
// at the same time we can cut the load to server after the build run for more than 60s
|
||||
private int _webConsoleLineAggressiveDequeueCount = 0;
|
||||
private const int _webConsoleLineAggressiveDequeueLimit = 4 * 60;
|
||||
@@ -89,6 +89,10 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
Trace.Entering();
|
||||
|
||||
var serviceEndPoint = jobRequest.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
_jobServer.InitializeWebsocketClient(serviceEndPoint);
|
||||
|
||||
if (_queueInProcess)
|
||||
{
|
||||
Trace.Info("No-opt, all queue process tasks are running.");
|
||||
@@ -156,6 +160,9 @@ namespace GitHub.Runner.Common
|
||||
await ProcessTimelinesUpdateQueueAsync(runOnce: true);
|
||||
Trace.Info("Timeline update queue drained.");
|
||||
|
||||
Trace.Info($"Disposing job server ...");
|
||||
await _jobServer.DisposeAsync();
|
||||
|
||||
Trace.Info("All queue process tasks have been stopped, and all queues are drained.");
|
||||
}
|
||||
|
||||
@@ -292,14 +299,10 @@ namespace GitHub.Runner.Common
|
||||
{
|
||||
try
|
||||
{
|
||||
// we will not requeue failed batch, since the web console lines are time sensitive.
|
||||
if (batch[0].LineNumber.HasValue)
|
||||
// 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.Value, default(CancellationToken));
|
||||
}
|
||||
else
|
||||
{
|
||||
await _jobServer.AppendTimelineRecordFeedAsync(_scopeIdentifier, _hubName, _planId, _jobTimelineId, _jobTimelineRecordId, stepRecordId, batch.Select(logLine => logLine.Line).ToList(), default(CancellationToken));
|
||||
await _jobServer.AppendTimelineRecordFeedAsync(_scopeIdentifier, _hubName, _planId, _jobTimelineId, _jobTimelineRecordId, stepRecordId, batch.Select(logLine => logLine.Line).ToList(), batch[0].LineNumber, timeoutTokenSource.Token);
|
||||
}
|
||||
|
||||
if (_firstConsoleOutputs)
|
||||
@@ -489,8 +492,8 @@ namespace GitHub.Runner.Common
|
||||
|
||||
if (runOnce)
|
||||
{
|
||||
// continue process timeline records update,
|
||||
// we might have more records need update,
|
||||
// continue process timeline records update,
|
||||
// we might have more records need update,
|
||||
// since we just create a new sub-timeline
|
||||
if (pendingSubtimelineUpdate)
|
||||
{
|
||||
|
||||
76
src/Runner.Common/RunServer.cs
Normal file
76
src/Runner.Common/RunServer.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.Pipelines;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.WebApi;
|
||||
|
||||
namespace GitHub.Runner.Common
|
||||
{
|
||||
[ServiceLocator(Default = typeof(RunServer))]
|
||||
public interface IRunServer : IRunnerService
|
||||
{
|
||||
Task ConnectAsync(Uri serverUrl, VssCredentials credentials);
|
||||
|
||||
Task<AgentJobRequestMessage> GetJobMessageAsync(string id);
|
||||
}
|
||||
|
||||
public sealed class RunServer : RunnerService, IRunServer
|
||||
{
|
||||
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 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)
|
||||
{
|
||||
throw new InvalidOperationException($"SetConnection");
|
||||
}
|
||||
}
|
||||
|
||||
public Task<AgentJobRequestMessage> GetJobMessageAsync(string id)
|
||||
{
|
||||
CheckConnection();
|
||||
return _taskAgentClient.GetJobMessageAsync(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<OutputType>Library</OutputType>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<NoWarn>NU1701;NU1603</NoWarn>
|
||||
<Version>$(Version)</Version>
|
||||
|
||||
@@ -6,7 +6,13 @@ namespace GitHub.Runner.Common.Util
|
||||
public static class NodeUtil
|
||||
{
|
||||
private const string _defaultNodeVersion = "node16";
|
||||
public static readonly ReadOnlyCollection<string> BuiltInNodeVersions = new(new[] {"node12", "node16"});
|
||||
|
||||
#if OS_OSX && ARM64
|
||||
public static readonly ReadOnlyCollection<string> BuiltInNodeVersions = new(new[] { "node16" });
|
||||
#else
|
||||
public static readonly ReadOnlyCollection<string> BuiltInNodeVersions = new(new[] { "node12", "node16" });
|
||||
#endif
|
||||
|
||||
public static string GetInternalNodeVersion()
|
||||
{
|
||||
var forcedNodeVersion = Environment.GetEnvironmentVariable(Constants.Variables.Agent.ForcedInternalNodeVersion);
|
||||
|
||||
@@ -17,43 +17,57 @@ namespace GitHub.Runner.Listener
|
||||
private readonly IPromptManager _promptManager;
|
||||
private readonly Tracing _trace;
|
||||
|
||||
private readonly string[] validCommands =
|
||||
// Valid flags for all commands
|
||||
private readonly string[] genericOptions =
|
||||
{
|
||||
Constants.Runner.CommandLine.Commands.Configure,
|
||||
Constants.Runner.CommandLine.Commands.Remove,
|
||||
Constants.Runner.CommandLine.Commands.Run,
|
||||
Constants.Runner.CommandLine.Commands.Warmup,
|
||||
};
|
||||
|
||||
private readonly string[] validFlags =
|
||||
{
|
||||
Constants.Runner.CommandLine.Flags.Check,
|
||||
Constants.Runner.CommandLine.Flags.Commit,
|
||||
Constants.Runner.CommandLine.Flags.DisableUpdate,
|
||||
Constants.Runner.CommandLine.Flags.Ephemeral,
|
||||
Constants.Runner.CommandLine.Flags.Help,
|
||||
Constants.Runner.CommandLine.Flags.Once,
|
||||
Constants.Runner.CommandLine.Flags.Replace,
|
||||
Constants.Runner.CommandLine.Flags.RunAsService,
|
||||
Constants.Runner.CommandLine.Flags.Unattended,
|
||||
Constants.Runner.CommandLine.Flags.Version
|
||||
Constants.Runner.CommandLine.Flags.Version,
|
||||
Constants.Runner.CommandLine.Flags.Commit,
|
||||
Constants.Runner.CommandLine.Flags.Check
|
||||
};
|
||||
|
||||
private readonly string[] validArgs =
|
||||
// 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[]>
|
||||
{
|
||||
Constants.Runner.CommandLine.Args.Auth,
|
||||
Constants.Runner.CommandLine.Args.Labels,
|
||||
Constants.Runner.CommandLine.Args.MonitorSocketAddress,
|
||||
Constants.Runner.CommandLine.Args.Name,
|
||||
Constants.Runner.CommandLine.Args.PAT,
|
||||
Constants.Runner.CommandLine.Args.RunnerGroup,
|
||||
Constants.Runner.CommandLine.Args.StartupType,
|
||||
Constants.Runner.CommandLine.Args.Token,
|
||||
Constants.Runner.CommandLine.Args.Url,
|
||||
Constants.Runner.CommandLine.Args.UserName,
|
||||
Constants.Runner.CommandLine.Args.WindowsLogonAccount,
|
||||
Constants.Runner.CommandLine.Args.WindowsLogonPassword,
|
||||
Constants.Runner.CommandLine.Args.Work
|
||||
// Valid configure flags and args
|
||||
[Constants.Runner.CommandLine.Commands.Configure] =
|
||||
new string[]
|
||||
{
|
||||
Constants.Runner.CommandLine.Flags.DisableUpdate,
|
||||
Constants.Runner.CommandLine.Flags.Ephemeral,
|
||||
Constants.Runner.CommandLine.Flags.Replace,
|
||||
Constants.Runner.CommandLine.Flags.RunAsService,
|
||||
Constants.Runner.CommandLine.Flags.Unattended,
|
||||
Constants.Runner.CommandLine.Args.Auth,
|
||||
Constants.Runner.CommandLine.Args.Labels,
|
||||
Constants.Runner.CommandLine.Args.MonitorSocketAddress,
|
||||
Constants.Runner.CommandLine.Args.Name,
|
||||
Constants.Runner.CommandLine.Args.PAT,
|
||||
Constants.Runner.CommandLine.Args.RunnerGroup,
|
||||
Constants.Runner.CommandLine.Args.Token,
|
||||
Constants.Runner.CommandLine.Args.Url,
|
||||
Constants.Runner.CommandLine.Args.UserName,
|
||||
Constants.Runner.CommandLine.Args.WindowsLogonAccount,
|
||||
Constants.Runner.CommandLine.Args.WindowsLogonPassword,
|
||||
Constants.Runner.CommandLine.Args.Work
|
||||
},
|
||||
// Valid remove flags and args
|
||||
[Constants.Runner.CommandLine.Commands.Remove] =
|
||||
new string[]
|
||||
{
|
||||
Constants.Runner.CommandLine.Args.Token,
|
||||
Constants.Runner.CommandLine.Args.PAT
|
||||
},
|
||||
// Valid run flags and args
|
||||
[Constants.Runner.CommandLine.Commands.Run] =
|
||||
new string[]
|
||||
{
|
||||
Constants.Runner.CommandLine.Flags.Once,
|
||||
Constants.Runner.CommandLine.Args.StartupType
|
||||
},
|
||||
// valid warmup flags and args
|
||||
[Constants.Runner.CommandLine.Commands.Warmup] =
|
||||
new string[] { }
|
||||
};
|
||||
|
||||
// Commands.
|
||||
@@ -126,17 +140,48 @@ namespace GitHub.Runner.Listener
|
||||
List<string> unknowns = new List<string>();
|
||||
|
||||
// detect unknown commands
|
||||
unknowns.AddRange(_parser.Commands.Where(x => !validCommands.Contains(x, StringComparer.OrdinalIgnoreCase)));
|
||||
unknowns.AddRange(_parser.Commands.Where(x => !validOptions.Keys.Contains(x, StringComparer.OrdinalIgnoreCase)));
|
||||
|
||||
// detect unknown flags
|
||||
unknowns.AddRange(_parser.Flags.Where(x => !validFlags.Contains(x, StringComparer.OrdinalIgnoreCase)));
|
||||
|
||||
// detect unknown args
|
||||
unknowns.AddRange(_parser.Args.Keys.Where(x => !validArgs.Contains(x, StringComparer.OrdinalIgnoreCase)));
|
||||
if (unknowns.Count == 0)
|
||||
{
|
||||
// detect unknown flags and args for valid commands
|
||||
foreach (var command in _parser.Commands)
|
||||
{
|
||||
if (validOptions.TryGetValue(command, out string[] options))
|
||||
{
|
||||
unknowns.AddRange(_parser.Flags.Where(x => !options.Contains(x, StringComparer.OrdinalIgnoreCase) && !genericOptions.Contains(x, StringComparer.OrdinalIgnoreCase)));
|
||||
unknowns.AddRange(_parser.Args.Keys.Where(x => !options.Contains(x, StringComparer.OrdinalIgnoreCase)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return unknowns;
|
||||
}
|
||||
|
||||
public string GetCommandName()
|
||||
{
|
||||
string command = string.Empty;
|
||||
|
||||
if (Configure)
|
||||
{
|
||||
command = Constants.Runner.CommandLine.Commands.Configure;
|
||||
}
|
||||
else if (Remove)
|
||||
{
|
||||
command = Constants.Runner.CommandLine.Commands.Remove;
|
||||
}
|
||||
else if (Run)
|
||||
{
|
||||
command = Constants.Runner.CommandLine.Commands.Run;
|
||||
}
|
||||
else if (Warmup)
|
||||
{
|
||||
command = Constants.Runner.CommandLine.Commands.Warmup;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
//
|
||||
// Interactive flags.
|
||||
//
|
||||
|
||||
@@ -624,9 +624,12 @@ 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;
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -634,11 +637,6 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
var jsonResponse = await response.Content.ReadAsStringAsync();
|
||||
return StringUtil.ConvertFromJson<GitHubRunnerRegisterToken>(jsonResponse);
|
||||
}
|
||||
else if(response.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
// It doesn't make sense to retry in this case, so just stop
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
_term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'");
|
||||
@@ -647,15 +645,15 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
}
|
||||
catch(Exception ex) when (retryCount < 2)
|
||||
catch(Exception ex) when (retryCount < 2 && responseStatus != System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
retryCount++;
|
||||
Trace.Error($"Failed to get JIT runner token -- Atempt: {retryCount}");
|
||||
Trace.Error(ex);
|
||||
Trace.Info("Retrying in 5 seconds");
|
||||
}
|
||||
}
|
||||
var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5));
|
||||
Trace.Info($"Retrying in {backOff.Seconds} seconds");
|
||||
await Task.Delay(backOff);
|
||||
}
|
||||
return null;
|
||||
@@ -689,9 +687,11 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
{"runner_event", runnerEvent}
|
||||
};
|
||||
|
||||
var responseStatus = System.Net.HttpStatusCode.OK;
|
||||
try
|
||||
{
|
||||
var response = await httpClient.PostAsync(githubApiUrl, new StringContent(StringUtil.ConvertToJson(bodyObject), null, "application/json"));
|
||||
responseStatus = response.StatusCode;
|
||||
|
||||
if(response.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -699,29 +699,23 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
var jsonResponse = await response.Content.ReadAsStringAsync();
|
||||
return StringUtil.ConvertFromJson<GitHubAuthResult>(jsonResponse);
|
||||
}
|
||||
else if(response.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
// It doesn't make sense to retry in this case, so just stop
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
_term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'");
|
||||
var errorResponse = await response.Content.ReadAsStringAsync();
|
||||
_term.WriteError(errorResponse);
|
||||
// Something else bad happened, let's go to our retry logic
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
}
|
||||
catch(Exception ex) when (retryCount < 2)
|
||||
catch(Exception ex) when (retryCount < 2 && responseStatus != System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
retryCount++;
|
||||
Trace.Error($"Failed to get tenant credentials -- Atempt: {retryCount}");
|
||||
Trace.Error(ex);
|
||||
Trace.Info("Retrying in 5 seconds");
|
||||
}
|
||||
}
|
||||
var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5));
|
||||
Trace.Info($"Retrying in {backOff.Seconds} seconds");
|
||||
await Task.Delay(backOff);
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -95,7 +95,15 @@ namespace GitHub.Runner.Listener
|
||||
var unknownCommandlines = command.Validate();
|
||||
if (unknownCommandlines.Count > 0)
|
||||
{
|
||||
terminal.WriteError($"Unrecognized command-line input arguments: '{string.Join(", ", unknownCommandlines)}'. For usage refer to: .\\config.cmd --help or ./config.sh --help");
|
||||
string commandName = command.GetCommandName();
|
||||
if (string.IsNullOrEmpty(commandName))
|
||||
{
|
||||
terminal.WriteError($"This command does not recognize the command-line input arguments: '{string.Join(", ", unknownCommandlines)}'. For usage refer to: .\\config.cmd --help or ./config.sh --help");
|
||||
}
|
||||
else
|
||||
{
|
||||
terminal.WriteError($"Unrecognized command-line input arguments for command {commandName}: '{string.Join(", ", unknownCommandlines)}'. For usage refer to: .\\config.cmd --help or ./config.sh --help");
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the Runner class to execute the command.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<NoWarn>NU1701;NU1603</NoWarn>
|
||||
<Version>$(Version)</Version>
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Listener.Configuration;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Services.WebApi;
|
||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Sdk;
|
||||
using System.Linq;
|
||||
using GitHub.Runner.Listener.Check;
|
||||
using System.Collections.Generic;
|
||||
using GitHub.Runner.Listener.Configuration;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Services.WebApi;
|
||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||
using GitHub.DistributedTask.Pipelines;
|
||||
using GitHub.Services.Common;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
namespace GitHub.Runner.Listener
|
||||
{
|
||||
@@ -322,6 +328,7 @@ namespace GitHub.Runner.Listener
|
||||
|
||||
// Should we try to cleanup ephemeral runners
|
||||
bool runOnceJobCompleted = false;
|
||||
bool skipSessionDeletion = false;
|
||||
try
|
||||
{
|
||||
var notification = HostContext.GetService<IJobNotification>();
|
||||
@@ -457,6 +464,42 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
}
|
||||
}
|
||||
// Broker flow
|
||||
else if (string.Equals(message.MessageType, JobRequestMessageTypes.RunnerJobRequest, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (autoUpdateInProgress || runOnceJobReceived)
|
||||
{
|
||||
skipMessageDeletion = true;
|
||||
Trace.Info($"Skip message deletion for job request message '{message.MessageId}'.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var messageRef = StringUtil.ConvertFromJson<RunnerJobRequestRef>(message.Body);
|
||||
|
||||
// Create connection
|
||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
||||
var creds = credMgr.LoadCredentials();
|
||||
|
||||
// todo: add retries https://github.com/github/actions-broker/issues/49
|
||||
var runServer = HostContext.CreateService<IRunServer>();
|
||||
await runServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
||||
|
||||
var jobMessage = await RetriesHelper<AgentJobRequestMessage>.RetryWithTimeoutAsync(async () =>
|
||||
{
|
||||
return await runServer.GetJobMessageAsync(messageRef.RunnerRequestId);
|
||||
},
|
||||
TimeSpan.FromSeconds(5),
|
||||
TimeSpan.FromSeconds(10),
|
||||
5);
|
||||
|
||||
jobDispatcher.Run(jobMessage, runOnce);
|
||||
if (runOnce)
|
||||
{
|
||||
Trace.Info("One time used runner received job message.");
|
||||
runOnceJobReceived = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (string.Equals(message.MessageType, JobCancelMessage.MessageType, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var cancelJobMessage = JsonUtility.FromString<JobCancelMessage>(message.Body);
|
||||
@@ -468,6 +511,14 @@ namespace GitHub.Runner.Listener
|
||||
Trace.Info($"Skip message deletion for cancellation message '{message.MessageId}'.");
|
||||
}
|
||||
}
|
||||
else if (string.Equals(message.MessageType, Pipelines.HostedRunnerShutdownMessage.MessageType, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var HostedRunnerShutdownMessage = JsonUtility.FromString<Pipelines.HostedRunnerShutdownMessage>(message.Body);
|
||||
skipMessageDeletion = true;
|
||||
skipSessionDeletion = true;
|
||||
Trace.Info($"Service requests the hosted runner to shutdown. Reason: '{HostedRunnerShutdownMessage.Reason}'.");
|
||||
return Constants.Runner.ReturnCode.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace.Error($"Received message {message.MessageId} with unsupported message type {message.MessageType}.");
|
||||
@@ -501,15 +552,18 @@ namespace GitHub.Runner.Listener
|
||||
await jobDispatcher.ShutdownAsync();
|
||||
}
|
||||
|
||||
try
|
||||
if (!skipSessionDeletion)
|
||||
{
|
||||
await _listener.DeleteSessionAsync();
|
||||
}
|
||||
catch (Exception ex) when (runOnce)
|
||||
{
|
||||
// ignore exception during delete session for ephemeral runner since the runner might already be deleted from the server side
|
||||
// and the delete session call will ends up with 401.
|
||||
Trace.Info($"Ignore any exception during DeleteSession for an ephemeral runner. {ex}");
|
||||
try
|
||||
{
|
||||
await _listener.DeleteSessionAsync();
|
||||
}
|
||||
catch (Exception ex) when (runOnce)
|
||||
{
|
||||
// ignore exception during delete session for ephemeral runner since the runner might already be deleted from the server side
|
||||
// and the delete session call will ends up with 401.
|
||||
Trace.Info($"Ignore any exception during DeleteSession for an ephemeral runner. {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
messageQueueLoopTokenSource.Dispose();
|
||||
|
||||
13
src/Runner.Listener/RunnerJobRequestRef.cs
Normal file
13
src/Runner.Listener/RunnerJobRequestRef.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace GitHub.Runner.Listener
|
||||
{
|
||||
[DataContract]
|
||||
public sealed class RunnerJobRequestRef
|
||||
{
|
||||
[DataMember(Name = "id")]
|
||||
public string Id { get; set; }
|
||||
[DataMember(Name = "runner_request_id")]
|
||||
public string RunnerRequestId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<NoWarn>NU1701;NU1603</NoWarn>
|
||||
<Version>$(Version)</Version>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<OutputType>Library</OutputType>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<NoWarn>NU1701;NU1603</NoWarn>
|
||||
<Version>$(Version)</Version>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<OutputType>Library</OutputType>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<NoWarn>NU1701;NU1603</NoWarn>
|
||||
<Version>$(Version)</Version>
|
||||
|
||||
@@ -57,6 +57,10 @@ namespace GitHub.Runner.Sdk
|
||||
settings.SendTimeout = TimeSpan.FromSeconds(Math.Min(Math.Max(httpRequestTimeoutSeconds, 100), 1200));
|
||||
}
|
||||
|
||||
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_ALLOW_REDIRECT")))
|
||||
{
|
||||
settings.AllowAutoRedirect = true;
|
||||
}
|
||||
|
||||
// Remove Invariant from the list of accepted languages.
|
||||
//
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using GitHub.Runner.Sdk;
|
||||
|
||||
namespace GitHub.Runner.Sdk
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.ObjectTemplating;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||
@@ -141,21 +140,7 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
IStepHost stepHost = HostContext.CreateService<IDefaultStepHost>();
|
||||
|
||||
// Makes directory for event_path data
|
||||
var tempDirectory = HostContext.GetDirectory(WellKnownDirectory.Temp);
|
||||
var workflowDirectory = Path.Combine(tempDirectory, "_github_workflow");
|
||||
Directory.CreateDirectory(workflowDirectory);
|
||||
|
||||
var gitHubEvent = ExecutionContext.GetGitHubContext("event");
|
||||
|
||||
// adds the GitHub event path/file if the event exists
|
||||
if (gitHubEvent != null)
|
||||
{
|
||||
var workflowFile = Path.Combine(workflowDirectory, "event.json");
|
||||
Trace.Info($"Write event payload to {workflowFile}");
|
||||
File.WriteAllText(workflowFile, gitHubEvent, new UTF8Encoding(false));
|
||||
ExecutionContext.SetGitHubContext("event_path", workflowFile);
|
||||
}
|
||||
ExecutionContext.WriteWebhookPayload();
|
||||
|
||||
// Set GITHUB_ACTION_REPOSITORY if this Action is from a repository
|
||||
if (Action.Reference is Pipelines.RepositoryPathReference repoPathReferenceAction &&
|
||||
@@ -186,8 +171,16 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
// Load the inputs.
|
||||
ExecutionContext.Debug("Loading inputs");
|
||||
var templateEvaluator = ExecutionContext.ToPipelineTemplateEvaluator();
|
||||
var inputs = templateEvaluator.EvaluateStepInputs(Action.Inputs, ExecutionContext.ExpressionValues, ExecutionContext.ExpressionFunctions);
|
||||
Dictionary<string, string> inputs;
|
||||
if (ExecutionContext.Global.Variables.GetBoolean(Constants.Runner.Features.UseContainerPathForTemplate) ?? false)
|
||||
{
|
||||
inputs = EvaluateStepInputs(stepHost);
|
||||
}
|
||||
else
|
||||
{
|
||||
var templateEvaluator = ExecutionContext.ToPipelineTemplateEvaluator();
|
||||
inputs = templateEvaluator.EvaluateStepInputs(Action.Inputs, ExecutionContext.ExpressionValues, ExecutionContext.ExpressionFunctions);
|
||||
}
|
||||
|
||||
var userInputs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (KeyValuePair<string, string> input in inputs)
|
||||
@@ -314,6 +307,15 @@ namespace GitHub.Runner.Worker
|
||||
return didFullyEvaluate;
|
||||
}
|
||||
|
||||
private Dictionary<String, String> EvaluateStepInputs(IStepHost stepHost)
|
||||
{
|
||||
DictionaryContextData expressionValues = ExecutionContext.GetExpressionValues(stepHost);
|
||||
var templateEvaluator = ExecutionContext.ToPipelineTemplateEvaluator();
|
||||
var inputs = templateEvaluator.EvaluateStepInputs(Action.Inputs, expressionValues, ExecutionContext.ExpressionFunctions);
|
||||
|
||||
return inputs;
|
||||
}
|
||||
|
||||
private string GenerateDisplayName(ActionStep action, DictionaryContextData contextData, IExecutionContext context, out bool didFullyEvaluate)
|
||||
{
|
||||
ArgUtil.NotNull(context, nameof(context));
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Sdk;
|
||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace GitHub.Runner.Worker.Container
|
||||
{
|
||||
public class ContainerInfo
|
||||
{
|
||||
private IDictionary<string, string> _userMountVolumes;
|
||||
private List<MountVolume> _mountVolumes;
|
||||
private IDictionary<string, string> _userPortMappings;
|
||||
private List<PortMapping> _portMappings;
|
||||
@@ -68,8 +68,7 @@ namespace GitHub.Runner.Worker.Container
|
||||
{
|
||||
foreach (var volume in container.Volumes)
|
||||
{
|
||||
UserMountVolumes[volume] = volume;
|
||||
MountVolumes.Add(new MountVolume(volume));
|
||||
MountVolumes.Add(new MountVolume(volume, isUserProvided: true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,19 +103,20 @@ namespace GitHub.Runner.Worker.Container
|
||||
return _environmentVariables;
|
||||
}
|
||||
}
|
||||
|
||||
public IDictionary<string, string> UserMountVolumes
|
||||
public ReadOnlyCollection<MountVolume> UserMountVolumes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_userMountVolumes == null)
|
||||
{
|
||||
_userMountVolumes = new Dictionary<string, string>();
|
||||
}
|
||||
return _userMountVolumes;
|
||||
return MountVolumes.Where(v => !string.IsNullOrEmpty(v.UserProvidedValue)).ToList().AsReadOnly();
|
||||
}
|
||||
}
|
||||
public ReadOnlyCollection<MountVolume> SystemMountVolumes
|
||||
{
|
||||
get
|
||||
{
|
||||
return MountVolumes.Where(v => string.IsNullOrEmpty(v.UserProvidedValue)).ToList().AsReadOnly();
|
||||
}
|
||||
}
|
||||
|
||||
public List<MountVolume> MountVolumes
|
||||
{
|
||||
get
|
||||
@@ -260,18 +260,27 @@ namespace GitHub.Runner.Worker.Container
|
||||
|
||||
public class MountVolume
|
||||
{
|
||||
public string UserProvidedValue { get; set; }
|
||||
public MountVolume(string sourceVolumePath, string targetVolumePath, bool readOnly = false)
|
||||
{
|
||||
this.SourceVolumePath = sourceVolumePath;
|
||||
this.TargetVolumePath = targetVolumePath;
|
||||
this.ReadOnly = readOnly;
|
||||
}
|
||||
|
||||
public MountVolume(string fromString)
|
||||
{
|
||||
ParseVolumeString(fromString);
|
||||
}
|
||||
|
||||
public MountVolume(string fromString, bool isUserProvided)
|
||||
{
|
||||
ParseVolumeString(fromString);
|
||||
if (isUserProvided)
|
||||
{
|
||||
UserProvidedValue = fromString;
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseVolumeString(string volume)
|
||||
{
|
||||
var volumeSplit = volume.Split(":");
|
||||
|
||||
@@ -192,13 +192,12 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
Trace.Info($"User provided port: {port.Value}");
|
||||
}
|
||||
foreach (var volume in container.UserMountVolumes)
|
||||
foreach (var mount in container.UserMountVolumes)
|
||||
{
|
||||
Trace.Info($"User provided volume: {volume.Value}");
|
||||
var mount = new MountVolume(volume.Value);
|
||||
Trace.Info($"User provided volume: {mount.UserProvidedValue}");
|
||||
if (string.Equals(mount.SourceVolumePath, "/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
executionContext.Warning($"Volume mount {volume.Value} is going to mount '/' into the container which may cause file ownership change in the entire file system and cause Actions Runner to lose permission to access the disk.");
|
||||
executionContext.Warning($"Volume mount {mount.UserProvidedValue} is going to mount '/' into the container which may cause file ownership change in the entire file system and cause Actions Runner to lose permission to access the disk.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using GitHub.DistributedTask.Expressions2;
|
||||
using GitHub.DistributedTask.Pipelines;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
@@ -19,7 +15,7 @@ using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Runner.Worker.Container;
|
||||
using GitHub.Services.WebApi;
|
||||
using GitHub.Runner.Worker.Handlers;
|
||||
using Newtonsoft.Json;
|
||||
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
|
||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||
@@ -109,6 +105,11 @@ namespace GitHub.Runner.Worker
|
||||
void ForceTaskComplete();
|
||||
void RegisterPostJobStep(IStep step);
|
||||
void PublishStepTelemetry();
|
||||
|
||||
void ApplyContinueOnError(TemplateToken continueOnError);
|
||||
void UpdateGlobalStepsContext();
|
||||
|
||||
void WriteWebhookPayload();
|
||||
}
|
||||
|
||||
public sealed class ExecutionContext : RunnerService, IExecutionContext
|
||||
@@ -438,14 +439,19 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
_logger.End();
|
||||
|
||||
UpdateGlobalStepsContext();
|
||||
|
||||
return Result.Value;
|
||||
}
|
||||
|
||||
public void UpdateGlobalStepsContext()
|
||||
{
|
||||
// Skip if generated context name. Generated context names start with "__". After 3.2 the server will never send an empty context name.
|
||||
if (!string.IsNullOrEmpty(ContextName) && !ContextName.StartsWith("__", StringComparison.Ordinal))
|
||||
{
|
||||
Global.StepsContext.SetOutcome(ScopeName, ContextName, (Outcome ?? Result ?? TaskResult.Succeeded).ToActionResult());
|
||||
Global.StepsContext.SetConclusion(ScopeName, ContextName, (Result ?? TaskResult.Succeeded).ToActionResult());
|
||||
}
|
||||
|
||||
return Result.Value;
|
||||
}
|
||||
|
||||
public void SetRunnerContext(string name, string value)
|
||||
@@ -550,10 +556,15 @@ namespace GitHub.Runner.Worker
|
||||
issue.Message = issue.Message[.._maxIssueMessageLength];
|
||||
}
|
||||
|
||||
// Tracking the line number (logFileLineNumber) and step number (stepNumber) for each issue that gets created
|
||||
// Actions UI from the run summary page use both values to easily link to an exact locations in logs where annotations originate from
|
||||
if (_record.Order != null)
|
||||
{
|
||||
issue.Data["stepNumber"] = _record.Order.ToString();
|
||||
}
|
||||
|
||||
if (issue.Type == IssueType.Error)
|
||||
{
|
||||
// tracking line number for each issue in log file
|
||||
// log UI use this to navigate from issue to log
|
||||
if (!string.IsNullOrEmpty(logMessage))
|
||||
{
|
||||
long logLineNumber = Write(WellKnownTags.Error, logMessage);
|
||||
@@ -569,8 +580,6 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
else if (issue.Type == IssueType.Warning)
|
||||
{
|
||||
// tracking line number for each issue in log file
|
||||
// log UI use this to navigate from issue to log
|
||||
if (!string.IsNullOrEmpty(logMessage))
|
||||
{
|
||||
long logLineNumber = Write(WellKnownTags.Warning, logMessage);
|
||||
@@ -586,9 +595,6 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
else if (issue.Type == IssueType.Notice)
|
||||
{
|
||||
|
||||
// tracking line number for each issue in log file
|
||||
// log UI use this to navigate from issue to log
|
||||
if (!string.IsNullOrEmpty(logMessage))
|
||||
{
|
||||
long logLineNumber = Write(WellKnownTags.Notice, logMessage);
|
||||
@@ -991,6 +997,24 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteWebhookPayload()
|
||||
{
|
||||
// Makes directory for event_path data
|
||||
var tempDirectory = HostContext.GetDirectory(WellKnownDirectory.Temp);
|
||||
var workflowDirectory = Path.Combine(tempDirectory, "_github_workflow");
|
||||
Directory.CreateDirectory(workflowDirectory);
|
||||
var gitHubEvent = GetGitHubContext("event");
|
||||
|
||||
// adds the GitHub event path/file if the event exists
|
||||
if (gitHubEvent != null)
|
||||
{
|
||||
var workflowFile = Path.Combine(workflowDirectory, "event.json");
|
||||
Trace.Info($"Write event payload to {workflowFile}");
|
||||
File.WriteAllText(workflowFile, gitHubEvent, new UTF8Encoding(false));
|
||||
SetGitHubContext("event_path", workflowFile);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeTimelineRecord(Guid timelineId, Guid timelineRecordId, Guid? parentTimelineRecordId, string recordType, string displayName, string refName, int? order)
|
||||
{
|
||||
_mainTimelineId = timelineId;
|
||||
@@ -1045,6 +1069,36 @@ namespace GitHub.Runner.Worker
|
||||
var newGuid = Guid.NewGuid();
|
||||
return CreateChild(newGuid, displayName, newGuid.ToString("N"), null, null, ActionRunStage.Post, intraActionState, _childTimelineRecordOrder - Root.PostJobSteps.Count, siblingScopeName: siblingScopeName);
|
||||
}
|
||||
|
||||
public void ApplyContinueOnError(TemplateToken continueOnErrorToken)
|
||||
{
|
||||
if (Result != TaskResult.Failed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var continueOnError = false;
|
||||
try
|
||||
{
|
||||
var templateEvaluator = this.ToPipelineTemplateEvaluator();
|
||||
continueOnError = templateEvaluator.EvaluateStepContinueOnError(continueOnErrorToken, ExpressionValues, ExpressionFunctions);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Info("The step failed and an error occurred when attempting to determine whether to continue on error.");
|
||||
Trace.Error(ex);
|
||||
this.Error("The step failed and an error occurred when attempting to determine whether to continue on error.");
|
||||
this.Error(ex);
|
||||
}
|
||||
|
||||
if (continueOnError)
|
||||
{
|
||||
Outcome = Result;
|
||||
Result = TaskResult.Succeeded;
|
||||
Trace.Info($"Updated step result (continue on error)");
|
||||
}
|
||||
|
||||
UpdateGlobalStepsContext();
|
||||
}
|
||||
}
|
||||
|
||||
// The Error/Warning/etc methods are created as extension methods to simplify unit testing.
|
||||
@@ -1066,7 +1120,6 @@ namespace GitHub.Runner.Worker
|
||||
context.Error(ex.Message);
|
||||
context.Debug(ex.ToString());
|
||||
}
|
||||
|
||||
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
||||
public static void Error(this IExecutionContext context, string message)
|
||||
{
|
||||
@@ -1140,6 +1193,66 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
return new TemplateTraceWriter(context);
|
||||
}
|
||||
|
||||
public static DictionaryContextData GetExpressionValues(this IExecutionContext context, IStepHost stepHost)
|
||||
{
|
||||
if (stepHost is ContainerStepHost)
|
||||
{
|
||||
|
||||
var expressionValues = context.ExpressionValues.Clone() as DictionaryContextData;
|
||||
context.UpdatePathsInExpressionValues("github", expressionValues, stepHost);
|
||||
context.UpdatePathsInExpressionValues("runner", expressionValues, stepHost);
|
||||
return expressionValues;
|
||||
}
|
||||
else
|
||||
{
|
||||
return context.ExpressionValues.Clone() as DictionaryContextData;
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdatePathsInExpressionValues(this IExecutionContext context, string contextName, DictionaryContextData expressionValues, IStepHost stepHost)
|
||||
{
|
||||
var dict = expressionValues[contextName].AssertDictionary($"expected context {contextName} to be a dictionary");
|
||||
context.ResolvePathsInExpressionValuesDictionary(dict, stepHost);
|
||||
expressionValues[contextName] = dict;
|
||||
}
|
||||
|
||||
private static void ResolvePathsInExpressionValuesDictionary(this IExecutionContext context, DictionaryContextData dict, IStepHost stepHost)
|
||||
{
|
||||
foreach (var key in dict.Keys.ToList())
|
||||
{
|
||||
if (dict[key] is StringContextData)
|
||||
{
|
||||
var value = dict[key].ToString();
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
dict[key] = new StringContextData(stepHost.ResolvePathForStepHost(value));
|
||||
}
|
||||
}
|
||||
else if (dict[key] is DictionaryContextData)
|
||||
{
|
||||
var innerDict = dict[key].AssertDictionary("expected dictionary");
|
||||
context.ResolvePathsInExpressionValuesDictionary(innerDict, stepHost);
|
||||
var updatedDict = new DictionaryContextData();
|
||||
foreach (var k in innerDict.Keys.ToList())
|
||||
{
|
||||
updatedDict[k] = innerDict[k];
|
||||
}
|
||||
dict[key] = updatedDict;
|
||||
}
|
||||
else if (dict[key] is CaseSensitiveDictionaryContextData)
|
||||
{
|
||||
var innerDict = dict[key].AssertDictionary("expected dictionary");
|
||||
context.ResolvePathsInExpressionValuesDictionary(innerDict, stepHost);
|
||||
var updatedDict = new CaseSensitiveDictionaryContextData();
|
||||
foreach (var k in innerDict.Keys.ToList())
|
||||
{
|
||||
updatedDict[k] = innerDict[k];
|
||||
}
|
||||
dict[key] = updatedDict;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TemplateTraceWriter : ObjectTemplating.ITraceWriter
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
void InitializeFiles(IExecutionContext context, ContainerInfo container);
|
||||
void ProcessFiles(IExecutionContext context, ContainerInfo container);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public sealed class FileCommandManager : RunnerService, IFileCommandManager
|
||||
@@ -57,7 +57,7 @@ namespace GitHub.Runner.Worker
|
||||
TryDeleteFile(newPath);
|
||||
File.Create(newPath).Dispose();
|
||||
|
||||
var pathToSet = container != null ? container.TranslateToContainerPath(newPath) : newPath;
|
||||
var pathToSet = container != null ? container.TranslateToContainerPath(newPath) : newPath;
|
||||
context.SetGitHubContext(fileCommand.ContextName, pathToSet);
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
foreach (var fileCommand in _commandExtensions)
|
||||
{
|
||||
try
|
||||
try
|
||||
{
|
||||
fileCommand.ProcessCommand(context, Path.Combine(_fileCommandDirectory, fileCommand.FilePrefix + _fileSuffix),container);
|
||||
}
|
||||
@@ -266,7 +266,7 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
public sealed class CreateStepSummaryCommand : RunnerService, IFileCommandExtension
|
||||
{
|
||||
private const int _attachmentSizeLimit = 128 * 1024;
|
||||
public const int AttachmentSizeLimit = 1024 * 1024;
|
||||
|
||||
public string ContextName => "step_summary";
|
||||
public string FilePrefix => "step_summary_";
|
||||
@@ -275,12 +275,6 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
public void ProcessCommand(IExecutionContext context, string filePath, ContainerInfo container)
|
||||
{
|
||||
if (!context.Global.Variables.GetBoolean("DistributedTask.UploadStepSummary") ?? true)
|
||||
{
|
||||
Trace.Info("Step Summary is disabled; skipping attachment upload");
|
||||
return;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(filePath) || !File.Exists(filePath))
|
||||
{
|
||||
Trace.Info($"Step Summary file ({filePath}) does not exist; skipping attachment upload");
|
||||
@@ -296,9 +290,9 @@ namespace GitHub.Runner.Worker
|
||||
return;
|
||||
}
|
||||
|
||||
if (fileSize > _attachmentSizeLimit)
|
||||
if (fileSize > AttachmentSizeLimit)
|
||||
{
|
||||
context.Error(String.Format(Constants.Runner.UnsupportedSummarySize, _attachmentSizeLimit / 1024, fileSize / 1024));
|
||||
context.Error(String.Format(Constants.Runner.UnsupportedSummarySize, AttachmentSizeLimit / 1024, fileSize / 1024));
|
||||
Trace.Info($"Step Summary file ({filePath}) is too large ({fileSize} bytes); skipping attachment upload");
|
||||
|
||||
return;
|
||||
|
||||
@@ -8,10 +8,10 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
private readonly HashSet<string> _contextEnvAllowlist = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"action",
|
||||
"action_path",
|
||||
"action_ref",
|
||||
"action_repository",
|
||||
"action",
|
||||
"actor",
|
||||
"api_url",
|
||||
"base_ref",
|
||||
@@ -22,12 +22,12 @@ namespace GitHub.Runner.Worker
|
||||
"head_ref",
|
||||
"job",
|
||||
"path",
|
||||
"ref",
|
||||
"ref_name",
|
||||
"ref_protected",
|
||||
"ref_type",
|
||||
"repository",
|
||||
"ref",
|
||||
"repository_owner",
|
||||
"repository",
|
||||
"retention_days",
|
||||
"run_attempt",
|
||||
"run_id",
|
||||
@@ -35,6 +35,7 @@ namespace GitHub.Runner.Worker
|
||||
"server_url",
|
||||
"sha",
|
||||
"step_summary",
|
||||
"triggering_actor",
|
||||
"workflow",
|
||||
"workspace",
|
||||
};
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.Expressions2;
|
||||
@@ -13,7 +11,6 @@ using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Runner.Worker;
|
||||
using GitHub.Runner.Worker.Expressions;
|
||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||
|
||||
@@ -86,7 +83,7 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
|
||||
ExecutionContext.StepTelemetry.HasPreStep = Data.HasPre;
|
||||
ExecutionContext.StepTelemetry.HasPostStep = Data.HasPost;
|
||||
|
||||
|
||||
ExecutionContext.StepTelemetry.HasRunsStep = hasRunsStep;
|
||||
ExecutionContext.StepTelemetry.HasUsesStep = hasUsesStep;
|
||||
ExecutionContext.StepTelemetry.StepCount = steps.Count;
|
||||
@@ -407,7 +404,7 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
}
|
||||
|
||||
// Update context
|
||||
SetStepsContext(step);
|
||||
step.ExecutionContext.UpdateGlobalStepsContext();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,6 +449,8 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
SetStepConclusion(step, Common.Util.TaskResultUtil.MergeTaskResults(step.ExecutionContext.Result, step.ExecutionContext.CommandResult.Value));
|
||||
}
|
||||
|
||||
step.ExecutionContext.ApplyContinueOnError(step.ContinueOnError);
|
||||
|
||||
Trace.Info($"Step result: {step.ExecutionContext.Result}");
|
||||
step.ExecutionContext.Debug($"Finished: {step.DisplayName}");
|
||||
step.ExecutionContext.PublishStepTelemetry();
|
||||
@@ -460,16 +459,7 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
private void SetStepConclusion(IStep step, TaskResult result)
|
||||
{
|
||||
step.ExecutionContext.Result = result;
|
||||
SetStepsContext(step);
|
||||
}
|
||||
private void SetStepsContext(IStep step)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(step.ExecutionContext.ContextName) && !step.ExecutionContext.ContextName.StartsWith("__", StringComparison.Ordinal))
|
||||
{
|
||||
// TODO: when we support continue on error, we may need to do logic here to change conclusion based on the continue on error result
|
||||
step.ExecutionContext.Global.StepsContext.SetOutcome(step.ExecutionContext.ScopeName, step.ExecutionContext.ContextName, (step.ExecutionContext.Result ?? TaskResult.Succeeded).ToActionResult());
|
||||
step.ExecutionContext.Global.StepsContext.SetConclusion(step.ExecutionContext.ScopeName, step.ExecutionContext.ContextName, (step.ExecutionContext.Result ?? TaskResult.Succeeded).ToActionResult());
|
||||
}
|
||||
step.ExecutionContext.UpdateGlobalStepsContext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
protected IActionCommandManager ActionCommandManager { get; private set; }
|
||||
|
||||
public Pipelines.ActionStepDefinitionReference Action { get; set; }
|
||||
public bool IsActionStep => Action != null;
|
||||
public Dictionary<string, string> Environment { get; set; }
|
||||
public Variables RuntimeVariables { get; set; }
|
||||
public IExecutionContext ExecutionContext { get; set; }
|
||||
@@ -49,13 +50,18 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
// Print out action details
|
||||
PrintActionDetails(stage);
|
||||
|
||||
// Get telemetry for the action.
|
||||
PopulateActionTelemetry();
|
||||
// Get telemetry for the action
|
||||
PopulateActionTelemetry(stage);
|
||||
}
|
||||
|
||||
protected void PopulateActionTelemetry()
|
||||
protected void PopulateActionTelemetry(ActionRunStage stage)
|
||||
{
|
||||
if (Action.Type == Pipelines.ActionSourceType.ContainerRegistry)
|
||||
if (!IsActionStep)
|
||||
{
|
||||
ExecutionContext.StepTelemetry.Type = "runner";
|
||||
ExecutionContext.StepTelemetry.Action = $"{stage} Job Hook";
|
||||
}
|
||||
else if (Action.Type == Pipelines.ActionSourceType.ContainerRegistry)
|
||||
{
|
||||
ExecutionContext.StepTelemetry.Type = "docker";
|
||||
var registryAction = Action as Pipelines.ContainerRegistryReference;
|
||||
|
||||
@@ -3,6 +3,8 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.Pipelines;
|
||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Sdk;
|
||||
@@ -92,6 +94,14 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
workingDirectory = HostContext.GetDirectory(WellKnownDirectory.Work);
|
||||
}
|
||||
|
||||
#if OS_OSX
|
||||
if (string.Equals(Data.NodeVersion, "node12", StringComparison.OrdinalIgnoreCase) &&
|
||||
Constants.Runner.PlatformArchitecture.Equals(Constants.Architecture.Arm64))
|
||||
{
|
||||
ExecutionContext.Output($"The node12 is not supported on macOS ARM64 platform. Use node16 instead.");
|
||||
Data.NodeVersion = "node16";
|
||||
}
|
||||
#endif
|
||||
var nodeRuntimeVersion = await StepHost.DetermineNodeRuntimeVersion(ExecutionContext, Data.NodeVersion);
|
||||
string file = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), nodeRuntimeVersion, "bin", $"node{IOUtil.ExeExtension}");
|
||||
|
||||
@@ -112,6 +122,17 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
// Remove environment variable that may cause conflicts with the node within the runner.
|
||||
Environment.Remove("NODE_ICU_DATA"); // https://github.com/actions/runner/issues/795
|
||||
|
||||
if (Data.NodeVersion == "node12" && (ExecutionContext.Global.Variables.GetBoolean(Constants.Runner.Features.Node12Warning) ?? false))
|
||||
{
|
||||
if (!ExecutionContext.JobContext.ContainsKey("Node12ActionsWarnings"))
|
||||
{
|
||||
ExecutionContext.JobContext["Node12ActionsWarnings"] = new ArrayContextData();
|
||||
}
|
||||
var repoAction = Action as RepositoryPathReference;
|
||||
var actionDisplayName = new StringContextData(repoAction.Name ?? repoAction.Path); // local actions don't have a 'Name'
|
||||
ExecutionContext.JobContext["Node12ActionsWarnings"].AssertArray("Node12ActionsWarnings").Add(actionDisplayName);
|
||||
}
|
||||
|
||||
using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager))
|
||||
using (var stderrManager = new OutputManager(ExecutionContext, ActionCommandManager))
|
||||
{
|
||||
|
||||
@@ -151,6 +151,11 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
}
|
||||
}
|
||||
|
||||
if (line.Contains("fatal: unsafe repository", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_executionContext.StepTelemetry.ErrorMessages.Add(line);
|
||||
}
|
||||
|
||||
// Regular output
|
||||
_executionContext.Output(line);
|
||||
}
|
||||
|
||||
@@ -24,16 +24,22 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
|
||||
protected override void PrintActionDetails(ActionRunStage stage)
|
||||
{
|
||||
|
||||
if (stage == ActionRunStage.Post)
|
||||
// if we're executing a Job Extension, we won't have an 'Action'
|
||||
if (!IsActionStep)
|
||||
{
|
||||
throw new NotSupportedException("Script action should not have 'Post' job action.");
|
||||
if (Inputs.TryGetValue("path", out var path))
|
||||
{
|
||||
ExecutionContext.Output($"##[group]Run '{path}'");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Inputs 'path' must be set for job extensions");
|
||||
}
|
||||
}
|
||||
|
||||
Inputs.TryGetValue("script", out string contents);
|
||||
contents = contents ?? string.Empty;
|
||||
if (Action.Type == Pipelines.ActionSourceType.Script)
|
||||
else if (Action.Type == Pipelines.ActionSourceType.Script)
|
||||
{
|
||||
Inputs.TryGetValue("script", out string contents);
|
||||
contents = contents ?? string.Empty;
|
||||
var firstLine = contents.TrimStart(' ', '\t', '\r', '\n');
|
||||
var firstNewLine = firstLine.IndexOfAny(new[] { '\r', '\n' });
|
||||
if (firstNewLine >= 0)
|
||||
@@ -42,17 +48,16 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
}
|
||||
|
||||
ExecutionContext.Output($"##[group]Run {firstLine}");
|
||||
var multiLines = contents.Replace("\r\n", "\n").TrimEnd('\n').Split('\n');
|
||||
foreach (var line in multiLines)
|
||||
{
|
||||
// Bright Cyan color
|
||||
ExecutionContext.Output($"\x1b[36;1m{line}\x1b[0m");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid action type {Action.Type} for {nameof(ScriptHandler)}");
|
||||
}
|
||||
|
||||
var multiLines = contents.Replace("\r\n", "\n").TrimEnd('\n').Split('\n');
|
||||
foreach (var line in multiLines)
|
||||
{
|
||||
// Bright Cyan color
|
||||
ExecutionContext.Output($"\x1b[36;1m{line}\x1b[0m");
|
||||
throw new InvalidOperationException($"Invalid action type {Action?.Type} for {nameof(ScriptHandler)}");
|
||||
}
|
||||
|
||||
string argFormat;
|
||||
@@ -132,11 +137,6 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
|
||||
public async Task RunAsync(ActionRunStage stage)
|
||||
{
|
||||
if (stage == ActionRunStage.Post)
|
||||
{
|
||||
throw new NotSupportedException("Script action should not have 'Post' job action.");
|
||||
}
|
||||
|
||||
// Validate args
|
||||
Trace.Entering();
|
||||
ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext));
|
||||
@@ -153,7 +153,8 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
string workingDirectory = null;
|
||||
if (!Inputs.TryGetValue("workingDirectory", out workingDirectory))
|
||||
{
|
||||
if (string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.Global.JobDefaults.TryGetValue("run", out var runDefaults))
|
||||
// Don't use job level working directories for hooks
|
||||
if (IsActionStep && string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.Global.JobDefaults.TryGetValue("run", out var runDefaults))
|
||||
{
|
||||
if (runDefaults.TryGetValue("working-directory", out workingDirectory))
|
||||
{
|
||||
@@ -201,18 +202,37 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
}
|
||||
else
|
||||
{
|
||||
var parsed = ScriptHandlerHelpers.ParseShellOptionString(shell);
|
||||
shellCommand = parsed.shellCommand;
|
||||
// For non-ContainerStepHost, the command must be located on the host by Which
|
||||
commandPath = WhichUtil.Which(parsed.shellCommand, !isContainerStepHost, Trace, prependPath);
|
||||
argFormat = $"{parsed.shellArgs}".TrimStart();
|
||||
if (string.IsNullOrEmpty(argFormat))
|
||||
// For these shells, we want to use system binaries
|
||||
var systemShells = new string[] { "bash", "sh", "powershell", "pwsh" };
|
||||
if (!IsActionStep && systemShells.Contains(shell))
|
||||
{
|
||||
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shellCommand);
|
||||
shellCommand = shell;
|
||||
commandPath = WhichUtil.Which(shell, !isContainerStepHost, Trace, prependPath);
|
||||
if (shell == "bash")
|
||||
{
|
||||
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat("sh");
|
||||
}
|
||||
else
|
||||
{
|
||||
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shell);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var parsed = ScriptHandlerHelpers.ParseShellOptionString(shell);
|
||||
shellCommand = parsed.shellCommand;
|
||||
// For non-ContainerStepHost, the command must be located on the host by Which
|
||||
commandPath = WhichUtil.Which(parsed.shellCommand, !isContainerStepHost, Trace, prependPath);
|
||||
argFormat = $"{parsed.shellArgs}".TrimStart();
|
||||
if (string.IsNullOrEmpty(argFormat))
|
||||
{
|
||||
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shellCommand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(shellCommand))
|
||||
// Don't override runner telemetry here
|
||||
if (!string.IsNullOrEmpty(shellCommand) && IsActionStep)
|
||||
{
|
||||
ExecutionContext.StepTelemetry.Action = shellCommand;
|
||||
}
|
||||
@@ -222,10 +242,24 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
{
|
||||
throw new ArgumentException("Invalid shell option. Shell must be a valid built-in (bash, sh, cmd, powershell, pwsh) or a format string containing '{0}'");
|
||||
}
|
||||
|
||||
// We do not not the full path until we know what shell is being used, so that we can determine the file extension
|
||||
var scriptFilePath = Path.Combine(tempDirectory, $"{Guid.NewGuid()}{ScriptHandlerHelpers.GetScriptFileExtension(shellCommand)}");
|
||||
var resolvedScriptPath = $"{StepHost.ResolvePathForStepHost(scriptFilePath).Replace("\"", "\\\"")}";
|
||||
string scriptFilePath, resolvedScriptPath;
|
||||
if (IsActionStep)
|
||||
{
|
||||
// We do not not the full path until we know what shell is being used, so that we can determine the file extension
|
||||
scriptFilePath = Path.Combine(tempDirectory, $"{Guid.NewGuid()}{ScriptHandlerHelpers.GetScriptFileExtension(shellCommand)}");
|
||||
resolvedScriptPath = StepHost.ResolvePathForStepHost(scriptFilePath).Replace("\"", "\\\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
// JobExtensionRunners run a script file, we load that from the inputs here
|
||||
if (!Inputs.ContainsKey("path"))
|
||||
{
|
||||
throw new ArgumentException("Expected 'path' input to be set");
|
||||
}
|
||||
scriptFilePath = Inputs["path"];
|
||||
ArgUtil.NotNullOrEmpty(scriptFilePath, "path");
|
||||
resolvedScriptPath = Inputs["path"].Replace("\"", "\\\"");
|
||||
}
|
||||
|
||||
// Format arg string with script path
|
||||
var arguments = string.Format(argFormat, resolvedScriptPath);
|
||||
@@ -241,9 +275,12 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
#else
|
||||
// Don't add a BOM. It causes the script to fail on some operating systems (e.g. on Ubuntu 14).
|
||||
var encoding = new UTF8Encoding(false);
|
||||
#endif
|
||||
// Script is written to local path (ie host) but executed relative to the StepHost, which may be a container
|
||||
File.WriteAllText(scriptFilePath, contents, encoding);
|
||||
#endif
|
||||
if (IsActionStep)
|
||||
{
|
||||
// Script is written to local path (ie host) but executed relative to the StepHost, which may be a container
|
||||
File.WriteAllText(scriptFilePath, contents, encoding);
|
||||
}
|
||||
|
||||
// Prepend PATH
|
||||
AddPrependPathToEnvironment();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using GitHub.Runner.Sdk;
|
||||
|
||||
namespace GitHub.Runner.Worker.Handlers
|
||||
{
|
||||
@@ -79,5 +81,27 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
throw new ArgumentException($"Failed to parse COMMAND [..ARGS] from {shellOption}");
|
||||
}
|
||||
}
|
||||
|
||||
internal static string GetDefaultShellNameForScript(string path, Common.Tracing trace, string prependPath)
|
||||
{
|
||||
switch (Path.GetExtension(path))
|
||||
{
|
||||
case ".sh":
|
||||
// use 'sh' args but prefer bash
|
||||
if (WhichUtil.Which("bash", false, trace, prependPath) != null)
|
||||
{
|
||||
return "bash";
|
||||
}
|
||||
return "sh";
|
||||
case ".ps1":
|
||||
if (WhichUtil.Which("pwsh", false, trace, prependPath) != null)
|
||||
{
|
||||
return "pwsh";
|
||||
}
|
||||
return "powershell";
|
||||
default:
|
||||
throw new ArgumentException($"{path} is not a valid path to a script. Make sure it ends in '.sh' or '.ps1'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Worker.Container;
|
||||
using GitHub.Services.WebApi;
|
||||
using Newtonsoft.Json;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Sdk;
|
||||
using System.Linq;
|
||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||
|
||||
namespace GitHub.Runner.Worker.Handlers
|
||||
{
|
||||
|
||||
@@ -9,12 +9,14 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.Expressions2;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||
using GitHub.DistributedTask.Pipelines;
|
||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Runner.Worker;
|
||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||
|
||||
namespace GitHub.Runner.Worker
|
||||
@@ -205,6 +207,7 @@ namespace GitHub.Runner.Worker
|
||||
// Evaluate the job container
|
||||
context.Debug("Evaluating job container");
|
||||
var container = templateEvaluator.EvaluateJobContainer(message.JobContainer, jobContext.ExpressionValues, jobContext.ExpressionFunctions);
|
||||
ValidateJobContainer(container);
|
||||
if (container != null)
|
||||
{
|
||||
jobContext.Global.Container = new Container.ContainerInfo(HostContext, container);
|
||||
@@ -248,6 +251,19 @@ namespace GitHub.Runner.Worker
|
||||
Trace.Info("Downloading actions");
|
||||
var actionManager = HostContext.GetService<IActionManager>();
|
||||
var prepareResult = await actionManager.PrepareActionsAsync(context, message.Steps);
|
||||
|
||||
// add hook to preJobSteps
|
||||
var startedHookPath = Environment.GetEnvironmentVariable("ACTIONS_RUNNER_HOOK_JOB_STARTED");
|
||||
if (!string.IsNullOrEmpty(startedHookPath))
|
||||
{
|
||||
var hookProvider = HostContext.GetService<IJobHookProvider>();
|
||||
var jobHookData = new JobHookData(ActionRunStage.Pre, startedHookPath);
|
||||
preJobSteps.Add(new JobExtensionRunner(runAsync: hookProvider.RunHook,
|
||||
condition: $"{PipelineTemplateConstants.Always}()",
|
||||
displayName: Constants.Hooks.JobStartedStepName,
|
||||
data: (object)jobHookData));
|
||||
}
|
||||
|
||||
preJobSteps.AddRange(prepareResult.ContainerSetupSteps);
|
||||
|
||||
// Add start-container steps, record and stop-container steps
|
||||
@@ -337,6 +353,18 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
|
||||
// Register Job Completed hook if the variable is set
|
||||
var completedHookPath = Environment.GetEnvironmentVariable("ACTIONS_RUNNER_HOOK_JOB_COMPLETED");
|
||||
if (!string.IsNullOrEmpty(completedHookPath))
|
||||
{
|
||||
var hookProvider = HostContext.GetService<IJobHookProvider>();
|
||||
var jobHookData = new JobHookData(ActionRunStage.Post, completedHookPath);
|
||||
jobContext.RegisterPostJobStep(new JobExtensionRunner(runAsync: hookProvider.RunHook,
|
||||
condition: $"{PipelineTemplateConstants.Always}()",
|
||||
displayName: Constants.Hooks.JobCompletedStepName,
|
||||
data: (object)jobHookData));
|
||||
}
|
||||
|
||||
List<IStep> steps = new List<IStep>();
|
||||
steps.AddRange(preJobSteps);
|
||||
steps.AddRange(jobSteps);
|
||||
@@ -406,7 +434,7 @@ namespace GitHub.Runner.Worker
|
||||
// create a new timeline record node for 'Finalize job'
|
||||
IExecutionContext context = jobContext.CreateChild(Guid.NewGuid(), "Complete job", $"{nameof(JobExtension)}_Final", null, null, ActionRunStage.Post);
|
||||
context.StepTelemetry.Type = "runner";
|
||||
context.StepTelemetry.Action = "complete_joh";
|
||||
context.StepTelemetry.Action = "complete_job";
|
||||
using (var register = jobContext.CancellationToken.Register(() => { context.CancelToken(); }))
|
||||
{
|
||||
try
|
||||
@@ -646,5 +674,13 @@ namespace GitHub.Runner.Worker
|
||||
Trace.Info($"Total accessible running process: {snapshot.Count}.");
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
private static void ValidateJobContainer(JobContainer container)
|
||||
{
|
||||
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable(Constants.Variables.Actions.RequireJobContainer)) && container == null)
|
||||
{
|
||||
throw new ArgumentException("Jobs without a job container are forbidden on this runner, please add a 'container:' to your job or contact your self-hosted runner administrator.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
95
src/Runner.Worker/JobHookProvider.cs
Normal file
95
src/Runner.Worker/JobHookProvider.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Runner.Worker.Handlers;
|
||||
|
||||
namespace GitHub.Runner.Worker
|
||||
{
|
||||
[ServiceLocator(Default = typeof(JobHookProvider))]
|
||||
public interface IJobHookProvider : IRunnerService
|
||||
{
|
||||
Task RunHook(IExecutionContext executionContext, object data);
|
||||
}
|
||||
|
||||
public class JobHookData
|
||||
{
|
||||
public string Path { get; private set; }
|
||||
public ActionRunStage Stage { get; private set; }
|
||||
|
||||
public JobHookData(ActionRunStage stage, string path)
|
||||
{
|
||||
Path = path;
|
||||
Stage = stage;
|
||||
}
|
||||
}
|
||||
|
||||
public class JobHookProvider : RunnerService, IJobHookProvider
|
||||
{
|
||||
public override void Initialize(IHostContext hostContext)
|
||||
{
|
||||
base.Initialize(hostContext);
|
||||
}
|
||||
|
||||
public async Task RunHook(IExecutionContext executionContext, object data)
|
||||
{
|
||||
// Get Inputs
|
||||
var hookData = data as JobHookData;
|
||||
ArgUtil.NotNull(hookData, nameof(JobHookData));
|
||||
|
||||
var displayName = hookData.Stage == ActionRunStage.Pre ? "job started hook" : "job completed hook";
|
||||
// Log to users so that they know how this step was injected
|
||||
executionContext.Output($"A {displayName} has been configured by the self-hosted runner administrator");
|
||||
|
||||
// Validate script file.
|
||||
if (!File.Exists(hookData.Path))
|
||||
{
|
||||
throw new FileNotFoundException("File doesn't exist");
|
||||
}
|
||||
|
||||
executionContext.WriteWebhookPayload();
|
||||
|
||||
// Create the handler data.
|
||||
var scriptDirectory = Path.GetDirectoryName(hookData.Path);
|
||||
var stepHost = HostContext.CreateService<IDefaultStepHost>();
|
||||
var prependPath = string.Join(Path.PathSeparator.ToString(), executionContext.Global.PrependPath.Reverse<string>());
|
||||
Dictionary<string, string> inputs = new()
|
||||
{
|
||||
["path"] = hookData.Path,
|
||||
["shell"] = ScriptHandlerHelpers.GetDefaultShellNameForScript(hookData.Path, Trace, prependPath)
|
||||
};
|
||||
|
||||
// Create the handler
|
||||
var handlerFactory = HostContext.GetService<IHandlerFactory>();
|
||||
var handler = handlerFactory.Create(
|
||||
executionContext,
|
||||
action: null,
|
||||
stepHost,
|
||||
new ScriptActionExecutionData(),
|
||||
inputs,
|
||||
environment: new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
|
||||
executionContext.Global.Variables,
|
||||
actionDirectory: scriptDirectory,
|
||||
localActionContainerSetupSteps: null);
|
||||
handler.PrepareExecution(hookData.Stage);
|
||||
|
||||
// Setup file commands
|
||||
var fileCommandManager = HostContext.CreateService<IFileCommandManager>();
|
||||
fileCommandManager.InitializeFiles(executionContext, null);
|
||||
|
||||
// Run the step and process the file commands
|
||||
try
|
||||
{
|
||||
await handler.RunAsync(hookData.Stage);
|
||||
}
|
||||
finally
|
||||
{
|
||||
fileCommandManager.ProcessFiles(executionContext, executionContext.Global.Container);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Common;
|
||||
using GitHub.Runner.Common.Util;
|
||||
@@ -257,6 +258,12 @@ namespace GitHub.Runner.Worker
|
||||
}
|
||||
}
|
||||
|
||||
if (jobContext.JobContext.ContainsKey("Node12ActionsWarnings"))
|
||||
{
|
||||
var actions = string.Join(", ", jobContext.JobContext["Node12ActionsWarnings"].AssertArray("Node12ActionsWarnings").Select(action => action.ToString()));
|
||||
jobContext.Warning(string.Format(Constants.Runner.Node12DetectedAfterEndOfLife, actions));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await ShutdownQueue(throwOnFailure: true);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<NoWarn>NU1701;NU1603</NoWarn>
|
||||
<Version>$(Version)</Version>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -133,8 +134,10 @@ namespace GitHub.Runner.Worker
|
||||
// Test the condition again. The job was cancelled after the condition was originally evaluated.
|
||||
jobCancelRegister = jobContext.CancellationToken.Register(() =>
|
||||
{
|
||||
// Mark job as cancelled
|
||||
jobContext.Result = TaskResult.Canceled;
|
||||
// Mark job as Cancelled or Failed depending on HostContext shutdown token's cancellation
|
||||
jobContext.Result = HostContext.RunnerShutdownToken.IsCancellationRequested
|
||||
? TaskResult.Failed
|
||||
: TaskResult.Canceled;
|
||||
jobContext.JobContext.Status = jobContext.Result?.ToActionResult();
|
||||
|
||||
step.ExecutionContext.Debug($"Re-evaluate condition on job cancellation for step: '{step.DisplayName}'.");
|
||||
@@ -172,8 +175,10 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
if (jobContext.Result != TaskResult.Canceled)
|
||||
{
|
||||
// Mark job as cancelled
|
||||
jobContext.Result = TaskResult.Canceled;
|
||||
// Mark job as Cancelled or Failed depending on HostContext shutdown token's cancellation
|
||||
jobContext.Result = HostContext.RunnerShutdownToken.IsCancellationRequested
|
||||
? TaskResult.Failed
|
||||
: TaskResult.Canceled;
|
||||
jobContext.JobContext.Status = jobContext.Result?.ToActionResult();
|
||||
}
|
||||
}
|
||||
@@ -319,29 +324,8 @@ namespace GitHub.Runner.Worker
|
||||
step.ExecutionContext.Result = TaskResultUtil.MergeTaskResults(step.ExecutionContext.Result, step.ExecutionContext.CommandResult.Value);
|
||||
}
|
||||
|
||||
// Fixup the step result if ContinueOnError
|
||||
if (step.ExecutionContext.Result == TaskResult.Failed)
|
||||
{
|
||||
var continueOnError = false;
|
||||
try
|
||||
{
|
||||
continueOnError = templateEvaluator.EvaluateStepContinueOnError(step.ContinueOnError, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Info("The step failed and an error occurred when attempting to determine whether to continue on error.");
|
||||
Trace.Error(ex);
|
||||
step.ExecutionContext.Error("The step failed and an error occurred when attempting to determine whether to continue on error.");
|
||||
step.ExecutionContext.Error(ex);
|
||||
}
|
||||
step.ExecutionContext.ApplyContinueOnError(step.ContinueOnError);
|
||||
|
||||
if (continueOnError)
|
||||
{
|
||||
step.ExecutionContext.Outcome = step.ExecutionContext.Result;
|
||||
step.ExecutionContext.Result = TaskResult.Succeeded;
|
||||
Trace.Info($"Updated step result (continue on error)");
|
||||
}
|
||||
}
|
||||
Trace.Info($"Step result: {step.ExecutionContext.Result}");
|
||||
|
||||
// Complete the step context
|
||||
|
||||
@@ -129,9 +129,10 @@
|
||||
"required": true
|
||||
},
|
||||
"env": "step-env",
|
||||
"continue-on-error": "boolean-steps-context",
|
||||
"working-directory": "string-steps-context",
|
||||
"shell": {
|
||||
"type": "non-empty-string",
|
||||
"type": "string-steps-context",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
@@ -147,6 +148,7 @@
|
||||
"type": "non-empty-string",
|
||||
"required": true
|
||||
},
|
||||
"continue-on-error": "boolean-steps-context",
|
||||
"with": "step-with",
|
||||
"env": "step-env"
|
||||
}
|
||||
@@ -201,6 +203,20 @@
|
||||
],
|
||||
"string": {}
|
||||
},
|
||||
"boolean-steps-context": {
|
||||
"context": [
|
||||
"github",
|
||||
"inputs",
|
||||
"strategy",
|
||||
"matrix",
|
||||
"steps",
|
||||
"job",
|
||||
"runner",
|
||||
"env",
|
||||
"hashFiles(1,255)"
|
||||
],
|
||||
"boolean": {}
|
||||
},
|
||||
"step-env": {
|
||||
"context": [
|
||||
"github",
|
||||
|
||||
@@ -23,7 +23,6 @@ namespace GitHub.Services.Common.ClientStorage
|
||||
private readonly string m_filePath;
|
||||
private readonly VssFileStorageReader m_reader;
|
||||
private readonly IVssClientStorageWriter m_writer;
|
||||
|
||||
private const char c_defaultPathSeparator = '\\';
|
||||
private const bool c_defaultIgnoreCaseInPaths = false;
|
||||
|
||||
@@ -192,7 +191,7 @@ namespace GitHub.Services.Common.ClientStorage
|
||||
// Windows Impersonation is being used.
|
||||
|
||||
// Check to see if we can find the user's local application data directory.
|
||||
string subDir = "GitHub\\ActionsService";
|
||||
string subDir = Path.Combine("GitHub", "ActionsService");
|
||||
string path = Environment.GetEnvironmentVariable("localappdata");
|
||||
SafeGetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
if (string.IsNullOrEmpty(path))
|
||||
|
||||
41
src/Sdk/Common/Common/Utility/RetriesHelper.cs
Normal file
41
src/Sdk/Common/Common/Utility/RetriesHelper.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GitHub.Services.Common
|
||||
{
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
|
||||
public static class RetriesHelper<T>
|
||||
{
|
||||
public static async Task<T> RetryWithTimeoutAsync(
|
||||
Func<Task<T>> retriableAction,
|
||||
TimeSpan minBackoff,
|
||||
TimeSpan maxBackoff,
|
||||
int maxTimeoutMinutes = 5
|
||||
)
|
||||
{
|
||||
var remainingTime = TimeSpan.FromMinutes(maxTimeoutMinutes);
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await retriableAction();
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (remainingTime > TimeSpan.Zero)
|
||||
{
|
||||
var backOff = BackoffTimerHelper.GetRandomBackoff(minBackoff, maxBackoff);
|
||||
remainingTime -= backOff;
|
||||
await Task.Delay(backOff);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/Sdk/DTPipelines/Pipelines/RunnerShutdownMessage.cs
Normal file
39
src/Sdk/DTPipelines/Pipelines/RunnerShutdownMessage.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using GitHub.Services.WebApi;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace GitHub.DistributedTask.Pipelines
|
||||
{
|
||||
[DataContract]
|
||||
public sealed class HostedRunnerShutdownMessage
|
||||
{
|
||||
public static readonly String MessageType = "RunnerShutdown";
|
||||
|
||||
[JsonConstructor]
|
||||
internal HostedRunnerShutdownMessage()
|
||||
{
|
||||
}
|
||||
|
||||
public HostedRunnerShutdownMessage(String reason)
|
||||
{
|
||||
this.Reason = reason;
|
||||
}
|
||||
|
||||
[DataMember]
|
||||
public String Reason
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public WebApi.TaskAgentMessage GetAgentMessage()
|
||||
{
|
||||
return new WebApi.TaskAgentMessage
|
||||
{
|
||||
Body = JsonUtility.ToString(this),
|
||||
MessageType = HostedRunnerShutdownMessage.MessageType,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,5 +5,6 @@ namespace GitHub.DistributedTask.WebApi
|
||||
public static class JobRequestMessageTypes
|
||||
{
|
||||
public const String PipelineAgentJobRequest = "PipelineAgentJobRequest";
|
||||
public const String RunnerJobRequest = "RunnerJobRequest";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,6 +141,24 @@ namespace GitHub.DistributedTask.WebApi
|
||||
return ReplaceAgentAsync(poolId, agent.Id, agent, userState, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<Pipelines.AgentJobRequestMessage> GetJobMessageAsync(
|
||||
string messageId,
|
||||
object userState = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
HttpMethod httpMethod = new HttpMethod("GET");
|
||||
Guid locationId = new Guid("25adab70-1379-4186-be8e-b643061ebe3a");
|
||||
object routeValues = new { messageId = messageId };
|
||||
|
||||
return SendAsync<Pipelines.AgentJobRequestMessage>(
|
||||
httpMethod,
|
||||
locationId,
|
||||
routeValues: routeValues,
|
||||
version: new ApiResourceVersion(6.0, 1),
|
||||
userState: userState,
|
||||
cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
protected Task<T> SendAsync<T>(
|
||||
HttpMethod method,
|
||||
Guid locationId,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<OutputType>Library</OutputType>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<NoWarn>NU1701;NU1603</NoWarn>
|
||||
<Version>$(Version)</Version>
|
||||
|
||||
@@ -19,7 +19,8 @@ namespace GitHub.Runner.Common.Tests
|
||||
"linux-x64",
|
||||
"linux-arm",
|
||||
"linux-arm64",
|
||||
"osx-x64"
|
||||
"osx-x64",
|
||||
"osx-arm64"
|
||||
};
|
||||
|
||||
Assert.True(BuildConstants.Source.CommitHash.Length == 40, $"CommitHash should be SHA-1 hash {BuildConstants.Source.CommitHash}");
|
||||
|
||||
@@ -708,33 +708,85 @@ namespace GitHub.Runner.Common.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Theory]
|
||||
[InlineData("configure", "once")]
|
||||
[InlineData("remove", "disableupdate")]
|
||||
[InlineData("remove", "ephemeral")]
|
||||
[InlineData("remove", "once")]
|
||||
[InlineData("remove", "replace")]
|
||||
[InlineData("remove", "runasservice")]
|
||||
[InlineData("remove", "unattended")]
|
||||
[InlineData("run", "disableupdate")]
|
||||
[InlineData("run", "ephemeral")]
|
||||
[InlineData("run", "replace")]
|
||||
[InlineData("run", "runasservice")]
|
||||
[InlineData("run", "unattended")]
|
||||
[InlineData("warmup", "disableupdate")]
|
||||
[InlineData("warmup", "ephemeral")]
|
||||
[InlineData("warmup", "once")]
|
||||
[InlineData("warmup", "replace")]
|
||||
[InlineData("warmup", "runasservice")]
|
||||
[InlineData("warmup", "unattended")]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", nameof(CommandSettings))]
|
||||
public void ValidateFlags()
|
||||
public void ValidateInvalidFlagCommandCombination(string validCommand, string flag)
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
// Arrange.
|
||||
var command = new CommandSettings(hc, args: new string[] { "--badflag" });
|
||||
var command = new CommandSettings(hc, args: new string[] { validCommand, $"--{flag}" });
|
||||
|
||||
// Assert.
|
||||
Assert.Contains("badflag", command.Validate());
|
||||
Assert.Contains(flag, command.Validate());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Theory]
|
||||
[InlineData("remove", "auth", "bar arg value")]
|
||||
[InlineData("remove", "labels", "bar arg value")]
|
||||
[InlineData("remove", "monitorsocketaddress", "bar arg value")]
|
||||
[InlineData("remove", "name", "bar arg value")]
|
||||
[InlineData("remove", "runnergroup", "bar arg value")]
|
||||
[InlineData("remove", "url", "bar arg value")]
|
||||
[InlineData("remove", "username", "bar arg value")]
|
||||
[InlineData("remove", "windowslogonaccount", "bar arg value")]
|
||||
[InlineData("remove", "windowslogonpassword", "bar arg value")]
|
||||
[InlineData("remove", "work", "bar arg value")]
|
||||
[InlineData("run", "auth", "bad arg value")]
|
||||
[InlineData("run", "labels", "bad arg value")]
|
||||
[InlineData("run", "monitorsocketaddress", "bad arg value")]
|
||||
[InlineData("run", "name", "bad arg value")]
|
||||
[InlineData("run", "pat", "bad arg value")]
|
||||
[InlineData("run", "runnergroup", "bad arg value")]
|
||||
[InlineData("run", "token", "bad arg value")]
|
||||
[InlineData("run", "url", "bad arg value")]
|
||||
[InlineData("run", "username", "bad arg value")]
|
||||
[InlineData("run", "windowslogonaccount", "bad arg value")]
|
||||
[InlineData("run", "windowslogonpassword", "bad arg value")]
|
||||
[InlineData("run", "work", "bad arg value")]
|
||||
[InlineData("warmup", "auth", "bad arg value")]
|
||||
[InlineData("warmup", "labels", "bad arg value")]
|
||||
[InlineData("warmup", "monitorsocketaddress", "bad arg value")]
|
||||
[InlineData("warmup", "name", "bad arg value")]
|
||||
[InlineData("warmup", "pat", "bad arg value")]
|
||||
[InlineData("warmup", "runnergroup", "bad arg value")]
|
||||
[InlineData("warmup", "token", "bad arg value")]
|
||||
[InlineData("warmup", "url", "bad arg value")]
|
||||
[InlineData("warmup", "username", "bad arg value")]
|
||||
[InlineData("warmup", "windowslogonaccount", "bad arg value")]
|
||||
[InlineData("warmup", "windowslogonpassword", "bad arg value")]
|
||||
[InlineData("warmup", "work", "bad arg value")]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", nameof(CommandSettings))]
|
||||
public void ValidateArgs()
|
||||
public void ValidateInvalidArgCommandCombination(string validCommand, string arg, string argValue)
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
// Arrange.
|
||||
var command = new CommandSettings(hc, args: new string[] { "--badargname", "bad arg value" });
|
||||
var command = new CommandSettings(hc, args: new string[] { validCommand, $"--{arg}", argValue });
|
||||
|
||||
// Assert.
|
||||
Assert.Contains("badargname", command.Validate());
|
||||
Assert.Contains(arg, command.Validate());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -758,6 +810,73 @@ namespace GitHub.Runner.Common.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("configure", "help")]
|
||||
[InlineData("configure", "version")]
|
||||
[InlineData("configure", "commit")]
|
||||
[InlineData("configure", "check")]
|
||||
[InlineData("configure", "disableupdate")]
|
||||
[InlineData("configure", "ephemeral")]
|
||||
[InlineData("configure", "replace")]
|
||||
[InlineData("configure", "runasservice")]
|
||||
[InlineData("configure", "unattended")]
|
||||
[InlineData("remove", "help")]
|
||||
[InlineData("remove", "version")]
|
||||
[InlineData("remove", "commit")]
|
||||
[InlineData("remove", "check")]
|
||||
[InlineData("run", "help")]
|
||||
[InlineData("run", "version")]
|
||||
[InlineData("run", "commit")]
|
||||
[InlineData("run", "check")]
|
||||
[InlineData("run", "once")]
|
||||
[InlineData("warmup", "help")]
|
||||
[InlineData("warmup", "version")]
|
||||
[InlineData("warmup", "commit")]
|
||||
[InlineData("warmup", "check")]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", nameof(CommandSettings))]
|
||||
public void ValidateGoodFlagCommandCombination(string validCommand, string flag)
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
// Arrange.
|
||||
var command = new CommandSettings(hc, args: new string[] { validCommand, $"--{flag}" });
|
||||
|
||||
// Assert.
|
||||
Assert.True(command.Validate().Count == 0);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("configure", "auth", "good arg value")]
|
||||
[InlineData("configure", "labels", "good arg value")]
|
||||
[InlineData("configure", "monitorsocketaddress", "good arg value")]
|
||||
[InlineData("configure", "name", "good arg value")]
|
||||
[InlineData("configure", "pat", "good arg value")]
|
||||
[InlineData("configure", "runnergroup", "good arg value")]
|
||||
[InlineData("configure", "token", "good arg value")]
|
||||
[InlineData("configure", "url", "good arg value")]
|
||||
[InlineData("configure", "username", "good arg value")]
|
||||
[InlineData("configure", "windowslogonaccount", "good arg value")]
|
||||
[InlineData("configure", "windowslogonpassword", "good arg value")]
|
||||
[InlineData("configure", "work", "good arg value")]
|
||||
[InlineData("remove", "token", "good arg value")]
|
||||
[InlineData("remove", "pat", "good arg value")]
|
||||
[InlineData("run", "startuptype", "good arg value")]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", nameof(CommandSettings))]
|
||||
public void ValidateGoodArgCommandCombination(string validCommand, string arg, string argValue)
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
// Arrange.
|
||||
var command = new CommandSettings(hc, args: new string[] { validCommand, $"--{arg}", argValue });
|
||||
|
||||
// Assert.
|
||||
Assert.True(command.Validate().Count == 0);
|
||||
}
|
||||
}
|
||||
|
||||
private TestHostContext CreateTestContext([CallerMemberName] string testName = "")
|
||||
{
|
||||
TestHostContext hc = new TestHostContext(this, testName);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
#if !(OS_OSX && ARM64)
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -50,9 +51,9 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://github.com/actions/runner/releases/latest"));
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.Redirect)
|
||||
{
|
||||
var redirect = await response.Content.ReadAsStringAsync();
|
||||
var redirectUrl = response.Headers.Location.ToString();
|
||||
Regex regex = new Regex(@"/runner/releases/tag/v(?<version>\d+\.\d+\.\d+)");
|
||||
var match = regex.Match(redirect);
|
||||
var match = regex.Match(redirectUrl);
|
||||
if (match.Success)
|
||||
{
|
||||
latestVersion = match.Groups["version"].Value;
|
||||
@@ -63,6 +64,10 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
_packageUrl = $"https://github.com/actions/runner/releases/download/v{latestVersion}/actions-runner-{BuildConstants.RunnerPackage.PackageName}-{latestVersion}.zip";
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("The latest runner version could not be determined so a download URL could not be generated for it. Please check the location header of the redirect response of 'https://github.com/actions/runner/releases/latest'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -791,3 +796,4 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -166,9 +166,9 @@ namespace GitHub.Runner.Common.Tests
|
||||
string binDir = Path.Combine(TestUtil.GetSrcPath(), @"../_layout/bin");
|
||||
|
||||
#if OS_WINDOWS
|
||||
string node = Path.Combine(TestUtil.GetSrcPath(), @"..\_layout\externals\node12\bin\node");
|
||||
string node = Path.Combine(TestUtil.GetSrcPath(), @"..\_layout\externals\node16\bin\node");
|
||||
#else
|
||||
string node = Path.Combine(TestUtil.GetSrcPath(), @"../_layout/externals/node12/bin/node");
|
||||
string node = Path.Combine(TestUtil.GetSrcPath(), @"../_layout/externals/node16/bin/node");
|
||||
#endif
|
||||
string hashFilesScript = Path.Combine(binDir, "hashFiles");
|
||||
var hashResult = string.Empty;
|
||||
@@ -228,9 +228,9 @@ namespace GitHub.Runner.Common.Tests
|
||||
string binDir = Path.Combine(TestUtil.GetSrcPath(), @"../_layout/bin");
|
||||
|
||||
#if OS_WINDOWS
|
||||
string node = Path.Combine(TestUtil.GetSrcPath(), @"..\_layout\externals\node12\bin\node");
|
||||
string node = Path.Combine(TestUtil.GetSrcPath(), @"..\_layout\externals\node16\bin\node");
|
||||
#else
|
||||
string node = Path.Combine(TestUtil.GetSrcPath(), @"../_layout/externals/node12/bin/node");
|
||||
string node = Path.Combine(TestUtil.GetSrcPath(), @"../_layout/externals/node16/bin/node");
|
||||
#endif
|
||||
string hashFilesScript = Path.Combine(binDir, "hashFiles");
|
||||
var hashResult = string.Empty;
|
||||
|
||||
@@ -27,9 +27,9 @@ namespace GitHub.Runner.Common.Tests
|
||||
try
|
||||
{
|
||||
#if OS_WINDOWS
|
||||
string node = Path.Combine(TestUtil.GetSrcPath(), @"..\_layout\externals\node12\bin\node");
|
||||
string node = Path.Combine(TestUtil.GetSrcPath(), @"..\_layout\externals\node16\bin\node");
|
||||
#else
|
||||
string node = Path.Combine(TestUtil.GetSrcPath(), @"../_layout/externals/node12/bin/node");
|
||||
string node = Path.Combine(TestUtil.GetSrcPath(), @"../_layout/externals/node16/bin/node");
|
||||
hc.EnqueueInstance<IProcessInvoker>(new ProcessInvokerWrapper());
|
||||
#endif
|
||||
var startInfo = new ProcessStartInfo(node, "-e \"setTimeout(function(){{}}, 15 * 1000);\"");
|
||||
|
||||
@@ -118,7 +118,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
await _actionRunner.RunAsync();
|
||||
|
||||
//Assert
|
||||
_ec.Verify(x => x.SetGitHubContext("event_path", Path.Combine(_hc.GetDirectory(WellKnownDirectory.Temp), "_github_workflow", "event.json")), Times.Once);
|
||||
_ec.Verify(x => x.WriteWebhookPayload(), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -25,23 +25,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
private CreateStepSummaryCommand _createStepCommand;
|
||||
private ITraceWriter _trace;
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void CreateStepSummaryCommand_FeatureDisabled()
|
||||
{
|
||||
using (var hostContext = Setup(featureFlagState: "false"))
|
||||
{
|
||||
var stepSummaryFile = Path.Combine(_rootDirectory, "feature-off");
|
||||
|
||||
_createStepCommand.ProcessCommand(_executionContext.Object, stepSummaryFile, null);
|
||||
_jobExecutionContext.Complete();
|
||||
|
||||
_jobServerQueue.Verify(x => x.QueueFileUpload(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>()), Times.Never());
|
||||
Assert.Equal(0, _issues.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
@@ -117,7 +100,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stepSummaryFile = Path.Combine(_rootDirectory, "empty-file");
|
||||
File.WriteAllBytes(stepSummaryFile, new byte[128 * 1024 + 1]);
|
||||
File.WriteAllBytes(stepSummaryFile, new byte[CreateStepSummaryCommand.AttachmentSizeLimit + 1]);
|
||||
|
||||
_createStepCommand.ProcessCommand(_executionContext.Object, stepSummaryFile, null);
|
||||
_jobExecutionContext.Complete();
|
||||
@@ -199,7 +182,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
File.WriteAllText(path, contentStr, encoding);
|
||||
}
|
||||
|
||||
private TestHostContext Setup([CallerMemberName] string name = "", string featureFlagState = "true")
|
||||
private TestHostContext Setup([CallerMemberName] string name = "")
|
||||
{
|
||||
var hostContext = new TestHostContext(this, name);
|
||||
|
||||
@@ -241,7 +224,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
_variables = new Variables(hostContext, new Dictionary<string, VariableValue>
|
||||
{
|
||||
{ "MySecretName", new VariableValue("My secret value", true) },
|
||||
{ "DistributedTask.UploadStepSummary", featureFlagState },
|
||||
});
|
||||
|
||||
// Directory for test data
|
||||
|
||||
@@ -6,8 +6,11 @@ using System.Threading;
|
||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Worker;
|
||||
using GitHub.Runner.Worker.Container;
|
||||
using GitHub.Runner.Worker.Handlers;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests.Worker
|
||||
@@ -90,6 +93,63 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void ApplyContinueOnError_CheckResultAndOutcome()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
|
||||
// Arrange: Create a job request message.
|
||||
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
|
||||
TimelineReference timeline = new TimelineReference();
|
||||
Guid jobId = Guid.NewGuid();
|
||||
string jobName = "some job name";
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
|
||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||
{
|
||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||
Id = "github",
|
||||
Version = "sha1"
|
||||
});
|
||||
jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
|
||||
jobRequest.Variables["ACTIONS_STEP_DEBUG"] = "true";
|
||||
|
||||
// Arrange: Setup the paging logger.
|
||||
var pagingLogger = new Mock<IPagingLogger>();
|
||||
var jobServerQueue = new Mock<IJobServerQueue>();
|
||||
jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.IsAny<TimelineRecord>()));
|
||||
jobServerQueue.Setup(x => x.QueueWebConsoleLine(It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<long>())).Callback((Guid id, string msg, long? lineNumber) => { hc.GetTrace().Info(msg); });
|
||||
|
||||
hc.EnqueueInstance(pagingLogger.Object);
|
||||
hc.SetSingleton(jobServerQueue.Object);
|
||||
|
||||
var ec = new Runner.Worker.ExecutionContext();
|
||||
ec.Initialize(hc);
|
||||
|
||||
// Act.
|
||||
ec.InitializeJob(jobRequest, CancellationToken.None);
|
||||
|
||||
foreach (var tc in new List<(TemplateToken token, TaskResult result, TaskResult? expectedResult, TaskResult? expectedOutcome)> {
|
||||
(token: new BooleanToken(null, null, null, true), result: TaskResult.Failed, expectedResult: TaskResult.Succeeded, expectedOutcome: TaskResult.Failed),
|
||||
(token: new BooleanToken(null, null, null, true), result: TaskResult.Succeeded, expectedResult: TaskResult.Succeeded, expectedOutcome: null),
|
||||
(token: new BooleanToken(null, null, null, true), result: TaskResult.Canceled, expectedResult: TaskResult.Canceled, expectedOutcome: null),
|
||||
(token: new BooleanToken(null, null, null, false), result: TaskResult.Failed, expectedResult: TaskResult.Failed, expectedOutcome: null),
|
||||
(token: new BooleanToken(null, null, null, false), result: TaskResult.Succeeded, expectedResult: TaskResult.Succeeded, expectedOutcome: null),
|
||||
(token: new BooleanToken(null, null, null, false), result: TaskResult.Canceled, expectedResult: TaskResult.Canceled, expectedOutcome: null),
|
||||
})
|
||||
{
|
||||
ec.Result = tc.result;
|
||||
ec.Outcome = null;
|
||||
ec.ApplyContinueOnError(tc.token);
|
||||
Assert.Equal(ec.Result, tc.expectedResult);
|
||||
Assert.Equal(ec.Outcome, tc.expectedOutcome);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
@@ -144,6 +204,55 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void AddIssue_AddStepAndLineNumberInformation()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
|
||||
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
|
||||
TimelineReference timeline = new TimelineReference();
|
||||
Guid jobId = Guid.NewGuid();
|
||||
string jobName = "some job name";
|
||||
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
|
||||
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||
{
|
||||
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||
Id = "github",
|
||||
Version = "sha1"
|
||||
});
|
||||
jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
|
||||
|
||||
// Arrange: Setup the paging logger.
|
||||
var pagingLogger = new Mock<IPagingLogger>();
|
||||
var pagingLogger2 = new Mock<IPagingLogger>();
|
||||
var jobServerQueue = new Mock<IJobServerQueue>();
|
||||
jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.IsAny<TimelineRecord>()));
|
||||
|
||||
hc.EnqueueInstance(pagingLogger.Object);
|
||||
hc.EnqueueInstance(pagingLogger2.Object);
|
||||
hc.SetSingleton(jobServerQueue.Object);
|
||||
|
||||
var ec = new Runner.Worker.ExecutionContext();
|
||||
ec.Initialize(hc);
|
||||
ec.InitializeJob(jobRequest, CancellationToken.None);
|
||||
ec.Start();
|
||||
|
||||
var embeddedStep = ec.CreateChild(Guid.NewGuid(), "action_1_pre", "action_1_pre", null, null, ActionRunStage.Main, isEmbedded: true);
|
||||
embeddedStep.Start();
|
||||
|
||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Error, Message = "error annotation that should have step and line number information" });
|
||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning annotation that should have step and line number information" });
|
||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice annotation that should have step and line number information" });
|
||||
|
||||
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Data.ContainsKey("stepNumber") && i.Data.ContainsKey("logFileLineNumber") && i.Type == IssueType.Error).Count() == 1)), Times.AtLeastOnce);
|
||||
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Data.ContainsKey("stepNumber") && i.Data.ContainsKey("logFileLineNumber") && i.Type == IssueType.Warning).Count() == 1)), Times.AtLeastOnce);
|
||||
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Data.ContainsKey("stepNumber") && i.Data.ContainsKey("logFileLineNumber") && i.Type == IssueType.Notice).Count() == 1)), Times.AtLeastOnce);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
@@ -617,5 +726,149 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
|
||||
return hc;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void GetExpressionValues_ContainerStepHost()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
const string source = "/home/username/Projects/work/runner/_layout";
|
||||
var containerInfo = new ContainerInfo();
|
||||
containerInfo.ContainerId = "test";
|
||||
|
||||
containerInfo.AddPathTranslateMapping($"{source}/_work", "/__w");
|
||||
containerInfo.AddPathTranslateMapping($"{source}/_temp", "/__t");
|
||||
containerInfo.AddPathTranslateMapping($"{source}/externals", "/__e");
|
||||
|
||||
containerInfo.AddPathTranslateMapping($"{source}/_work/_temp/_github_home", "/github/home");
|
||||
containerInfo.AddPathTranslateMapping($"{source}/_work/_temp/_github_workflow", "/github/workflow");
|
||||
|
||||
foreach (var v in new List<string>() {
|
||||
$"{source}/_work",
|
||||
$"{source}/externals",
|
||||
$"{source}/_work/_temp",
|
||||
$"{source}/_work/_actions",
|
||||
$"{source}/_work/_tool",
|
||||
})
|
||||
{
|
||||
containerInfo.MountVolumes.Add(new MountVolume(v, containerInfo.TranslateToContainerPath(v)));
|
||||
};
|
||||
|
||||
var stepHost = new ContainerStepHost();
|
||||
stepHost.Container = containerInfo;
|
||||
|
||||
var ec = new Runner.Worker.ExecutionContext();
|
||||
ec.Initialize(hc);
|
||||
|
||||
var inputGithubContext = new GitHubContext();
|
||||
var inputeRunnerContext = new RunnerContext();
|
||||
|
||||
// string context data
|
||||
inputGithubContext["action_path"] = new StringContextData("/home/username/Projects/work/runner/_layout/_work/_actions/owner/composite/main");
|
||||
inputGithubContext["action"] = new StringContextData("__owner_composite");
|
||||
inputGithubContext["api_url"] = new StringContextData("https://api.github.com/custom/path");
|
||||
inputGithubContext["env"] = new StringContextData("/home/username/Projects/work/runner/_layout/_work/_temp/_runner_file_commands/set_env_265698aa-7f38-40f5-9316-5c01a3153672");
|
||||
inputGithubContext["path"] = new StringContextData("/home/username/Projects/work/runner/_layout/_work/_temp/_runner_file_commands/add_path_265698aa-7f38-40f5-9316-5c01a3153672");
|
||||
inputGithubContext["event_path"] = new StringContextData("/home/username/Projects/work/runner/_layout/_work/_temp/_github_workflow/event.json");
|
||||
inputGithubContext["repository"] = new StringContextData("owner/repo-name");
|
||||
inputGithubContext["run_id"] = new StringContextData("2033211332");
|
||||
inputGithubContext["workflow"] = new StringContextData("Name of Workflow");
|
||||
inputGithubContext["workspace"] = new StringContextData("/home/username/Projects/work/runner/_layout/_work/step-order/step-order");
|
||||
inputeRunnerContext["temp"] = new StringContextData("/home/username/Projects/work/runner/_layout/_work/_temp");
|
||||
inputeRunnerContext["tool_cache"] = new StringContextData("/home/username/Projects/work/runner/_layout/_work/_tool");
|
||||
|
||||
// dictionary context data
|
||||
var githubEvent = new DictionaryContextData();
|
||||
githubEvent["inputs"] = null;
|
||||
githubEvent["ref"] = new StringContextData("refs/heads/main");
|
||||
githubEvent["repository"] = new DictionaryContextData();
|
||||
githubEvent["sender"] = new DictionaryContextData();
|
||||
githubEvent["workflow"] = new StringContextData(".github/workflows/composite_step_host_translate.yaml");
|
||||
|
||||
inputGithubContext["event"] = githubEvent;
|
||||
|
||||
ec.ExpressionValues["github"] = inputGithubContext;
|
||||
ec.ExpressionValues["runner"] = inputeRunnerContext;
|
||||
|
||||
var ecExpect = new Runner.Worker.ExecutionContext();
|
||||
ecExpect.Initialize(hc);
|
||||
|
||||
var expectedGithubEvent = new DictionaryContextData();
|
||||
expectedGithubEvent["inputs"] = null;
|
||||
expectedGithubEvent["ref"] = new StringContextData("refs/heads/main");
|
||||
expectedGithubEvent["repository"] = new DictionaryContextData();
|
||||
expectedGithubEvent["sender"] = new DictionaryContextData();
|
||||
expectedGithubEvent["workflow"] = new StringContextData(".github/workflows/composite_step_host_translate.yaml");
|
||||
var expectedGithubContext = new GitHubContext();
|
||||
var expectedRunnerContext = new RunnerContext();
|
||||
expectedGithubContext["action_path"] = new StringContextData("/__w/_actions/owner/composite/main");
|
||||
expectedGithubContext["action"] = new StringContextData("__owner_composite");
|
||||
expectedGithubContext["api_url"] = new StringContextData("https://api.github.com/custom/path");
|
||||
expectedGithubContext["env"] = new StringContextData("/__w/_temp/_runner_file_commands/set_env_265698aa-7f38-40f5-9316-5c01a3153672");
|
||||
expectedGithubContext["path"] = new StringContextData("/__w/_temp/_runner_file_commands/add_path_265698aa-7f38-40f5-9316-5c01a3153672");
|
||||
expectedGithubContext["event_path"] = new StringContextData("/github/workflow/event.json");
|
||||
expectedGithubContext["repository"] = new StringContextData("owner/repo-name");
|
||||
expectedGithubContext["run_id"] = new StringContextData("2033211332");
|
||||
expectedGithubContext["workflow"] = new StringContextData("Name of Workflow");
|
||||
expectedGithubContext["workspace"] = new StringContextData("/__w/step-order/step-order");
|
||||
expectedGithubContext["event"] = expectedGithubEvent;
|
||||
expectedRunnerContext["temp"] = new StringContextData("/__w/_temp");
|
||||
expectedRunnerContext["tool_cache"] = new StringContextData("/__w/_tool");
|
||||
|
||||
ecExpect.ExpressionValues["github"] = expectedGithubContext;
|
||||
ecExpect.ExpressionValues["runner"] = expectedRunnerContext;
|
||||
|
||||
var translatedExpressionValues = ec.GetExpressionValues(stepHost);
|
||||
|
||||
foreach (var contextName in new string[] { "github", "runner" })
|
||||
{
|
||||
var dict = translatedExpressionValues[contextName].AssertDictionary($"expected context github to be a dictionary");
|
||||
var expectedExpressionValues = ecExpect.ExpressionValues[contextName].AssertDictionary("expect dict");
|
||||
foreach (var key in dict.Keys.ToList())
|
||||
{
|
||||
if (dict[key] is StringContextData)
|
||||
{
|
||||
var expect = dict[key].AssertString("expect string");
|
||||
var outcome = expectedExpressionValues[key].AssertString("expect string");
|
||||
Assert.Equal(expect.Value, outcome.Value);
|
||||
}
|
||||
else if (dict[key] is DictionaryContextData || dict[key] is CaseSensitiveDictionaryContextData)
|
||||
{
|
||||
var expectDict = dict[key].AssertDictionary("expect dict");
|
||||
var actualDict = expectedExpressionValues[key].AssertDictionary("expect dict");
|
||||
Assert.True(ExpressionValuesAssertEqual(expectDict, actualDict));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool ExpressionValuesAssertEqual(DictionaryContextData expect, DictionaryContextData actual)
|
||||
{
|
||||
foreach (var key in expect.Keys.ToList())
|
||||
{
|
||||
if (expect[key] is StringContextData)
|
||||
{
|
||||
var expectValue = expect[key].AssertString("expect string");
|
||||
var actualValue = actual[key].AssertString("expect string");
|
||||
if (expectValue.Equals(actualValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (expect[key] is DictionaryContextData || expect[key] is CaseSensitiveDictionaryContextData)
|
||||
{
|
||||
var expectDict = expect[key].AssertDictionary("expect dict");
|
||||
var actualDict = actual[key].AssertDictionary("expect dict");
|
||||
if (!ExpressionValuesAssertEqual(expectDict, actualDict))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
private Mock<IPagingLogger> _logger;
|
||||
private Mock<IContainerOperationProvider> _containerProvider;
|
||||
private Mock<IDiagnosticLogManager> _diagnosticLogManager;
|
||||
private Mock<IJobHookProvider> _jobHookProvider;
|
||||
|
||||
private CancellationTokenSource _tokenSource;
|
||||
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
|
||||
@@ -40,6 +41,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
_directoryManager = new Mock<IPipelineDirectoryManager>();
|
||||
_directoryManager.Setup(x => x.PrepareDirectory(It.IsAny<IExecutionContext>(), It.IsAny<Pipelines.WorkspaceOptions>()))
|
||||
.Returns(new TrackingConfig() { PipelineDirectory = "runner", WorkspaceDirectory = "runner/runner" });
|
||||
_jobHookProvider = new Mock<IJobHookProvider>();
|
||||
|
||||
IActionRunner step1 = new ActionRunner();
|
||||
IActionRunner step2 = new ActionRunner();
|
||||
@@ -111,7 +113,9 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
hc.SetSingleton(_containerProvider.Object);
|
||||
hc.SetSingleton(_directoryManager.Object);
|
||||
hc.SetSingleton(_diagnosticLogManager.Object);
|
||||
hc.SetSingleton(_jobHookProvider.Object);
|
||||
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // JobExecutionContext
|
||||
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // job start hook
|
||||
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // Initial Job
|
||||
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // step1
|
||||
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // step2
|
||||
@@ -120,6 +124,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // step5
|
||||
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // prepare1
|
||||
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // prepare2
|
||||
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // job complete hook
|
||||
|
||||
hc.EnqueueInstance<IActionRunner>(step1);
|
||||
hc.EnqueueInstance<IActionRunner>(step2);
|
||||
@@ -206,6 +211,24 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public async Task JobExtensionBuildFailsWithoutContainerIfRequired()
|
||||
{
|
||||
Environment.SetEnvironmentVariable(Constants.Variables.Actions.RequireJobContainer, "true");
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
var jobExtension = new JobExtension();
|
||||
jobExtension.Initialize(hc);
|
||||
|
||||
_actionManager.Setup(x => x.PrepareActionsAsync(It.IsAny<IExecutionContext>(), It.IsAny<IEnumerable<Pipelines.JobStep>>(), It.IsAny<Guid>()))
|
||||
.Returns(Task.FromResult(new PrepareResult(new List<JobExtensionRunner>() { new JobExtensionRunner(null, "", "prepare1", null), new JobExtensionRunner(null, "", "prepare2", null) }, new Dictionary<Guid, IActionRunner>())));
|
||||
|
||||
await Assert.ThrowsAsync<ArgumentException>(() => jobExtension.InitializeJob(_jobEc, _message));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
@@ -348,5 +371,62 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Assert.Equal(TaskResult.Succeeded, _jobEc.Result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public async Task EnsurePreAndPostHookStepsIfEnvExists()
|
||||
{
|
||||
Environment.SetEnvironmentVariable("ACTIONS_RUNNER_HOOK_JOB_STARTED", "/foo/bar");
|
||||
Environment.SetEnvironmentVariable("ACTIONS_RUNNER_HOOK_JOB_COMPLETED", "/bar/foo");
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
var jobExtension = new JobExtension();
|
||||
jobExtension.Initialize(hc);
|
||||
|
||||
_actionManager.Setup(x => x.PrepareActionsAsync(It.IsAny<IExecutionContext>(), It.IsAny<IEnumerable<Pipelines.JobStep>>(), It.IsAny<Guid>()))
|
||||
.Returns(Task.FromResult(new PrepareResult(new List<JobExtensionRunner>(), new Dictionary<Guid, IActionRunner>())));
|
||||
|
||||
List<IStep> result = await jobExtension.InitializeJob(_jobEc, _message);
|
||||
|
||||
var trace = hc.GetTrace();
|
||||
|
||||
var hookStart = result.First() as JobExtensionRunner;
|
||||
|
||||
jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow);
|
||||
|
||||
Assert.Equal(Constants.Hooks.JobStartedStepName, hookStart.DisplayName);
|
||||
Assert.Equal(Constants.Hooks.JobCompletedStepName, (_jobEc.PostJobSteps.Last() as JobExtensionRunner).DisplayName);
|
||||
}
|
||||
|
||||
Environment.SetEnvironmentVariable("ACTIONS_RUNNER_HOOK_JOB_STARTED", null);
|
||||
Environment.SetEnvironmentVariable("ACTIONS_RUNNER_HOOK_JOB_COMPLETED", null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void EnsureNoPreAndPostHookSteps()
|
||||
{
|
||||
using (TestHostContext hc = CreateTestContext())
|
||||
{
|
||||
var jobExtension = new JobExtension();
|
||||
jobExtension.Initialize(hc);
|
||||
|
||||
_message.ActionsEnvironment = null;
|
||||
|
||||
_jobEc = new Runner.Worker.ExecutionContext {Result = TaskResult.Succeeded};
|
||||
_jobEc.Initialize(hc);
|
||||
_jobEc.InitializeJob(_message, _tokenSource.Token);
|
||||
|
||||
var x = _jobEc.JobSteps;
|
||||
|
||||
jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow);
|
||||
|
||||
Assert.Equal(TaskResult.Succeeded, _jobEc.Result);
|
||||
Assert.Equal(0, _jobEc.PostJobSteps.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.CompilerServices;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Runner.Worker;
|
||||
using GitHub.Runner.Worker.Container;
|
||||
@@ -937,6 +937,19 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void CaptureTelemetryForGitUnsafeRepository()
|
||||
{
|
||||
using (Setup())
|
||||
using (_outputManager)
|
||||
{
|
||||
Process("fatal: unsafe repository ('/github/workspace' is owned by someone else)");
|
||||
Assert.Contains("fatal: unsafe repository ('/github/workspace' is owned by someone else)", _executionContext.Object.StepTelemetry.ErrorMessages);
|
||||
}
|
||||
}
|
||||
|
||||
private TestHostContext Setup(
|
||||
[CallerMemberName] string name = "",
|
||||
IssueMatchersConfig matchers = null,
|
||||
@@ -962,6 +975,8 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Variables = _variables,
|
||||
WriteDebug = true,
|
||||
});
|
||||
_executionContext.Setup(x => x.StepTelemetry)
|
||||
.Returns(new DTWebApi.ActionsStepTelemetry());
|
||||
_executionContext.Setup(x => x.GetMatchers())
|
||||
.Returns(matchers?.Matchers ?? new List<IssueMatcherConfig>());
|
||||
_executionContext.Setup(x => x.Add(It.IsAny<OnMatcherChanged>()))
|
||||
|
||||
@@ -7,6 +7,9 @@ using Xunit;
|
||||
using GitHub.Runner.Worker;
|
||||
using GitHub.Runner.Worker.Handlers;
|
||||
using GitHub.Runner.Worker.Container;
|
||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||
using System.Linq;
|
||||
using GitHub.DistributedTask.Pipelines;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests.Worker
|
||||
{
|
||||
@@ -29,6 +32,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
return hc;
|
||||
}
|
||||
|
||||
#if OS_LINUX
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
@@ -105,5 +109,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Assert.Equal("node16", nodeVersion);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,6 +622,40 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
_stepContext.SetOutcome("", stepContext.Object.ContextName, (stepContext.Object.Outcome ?? stepContext.Object.Result ?? TaskResult.Succeeded).ToActionResult());
|
||||
_stepContext.SetConclusion("", stepContext.Object.ContextName, (stepContext.Object.Result ?? TaskResult.Succeeded).ToActionResult());
|
||||
});
|
||||
|
||||
stepContext.Setup(x => x.UpdateGlobalStepsContext()).Callback(() =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(stepContext.Object.ContextName) && !stepContext.Object.ContextName.StartsWith("__", StringComparison.Ordinal))
|
||||
{
|
||||
stepContext.Object.Global.StepsContext.SetOutcome(stepContext.Object.ScopeName, stepContext.Object.ContextName, (stepContext.Object.Outcome ?? stepContext.Object.Result ?? TaskResult.Succeeded).ToActionResult());
|
||||
stepContext.Object.Global.StepsContext.SetConclusion(stepContext.Object.ScopeName, stepContext.Object.ContextName, (stepContext.Object.Result ?? TaskResult.Succeeded).ToActionResult());
|
||||
}
|
||||
});
|
||||
stepContext.Setup(x => x.ApplyContinueOnError(It.IsAny<TemplateToken>())).Callback((TemplateToken token) =>
|
||||
{
|
||||
if (stepContext.Object.Result != TaskResult.Failed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var continueOnError = false;
|
||||
try
|
||||
{
|
||||
var templateEvaluator = stepContext.Object.ToPipelineTemplateEvaluator();
|
||||
continueOnError = templateEvaluator.EvaluateStepContinueOnError(token, stepContext.Object.ExpressionValues, stepContext.Object.ExpressionFunctions);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
stepContext.Object.Error("The step failed and an error occurred when attempting to determine whether to continue on error.");
|
||||
stepContext.Object.Error(ex);
|
||||
}
|
||||
|
||||
if (continueOnError)
|
||||
{
|
||||
stepContext.Object.Outcome = stepContext.Object.Result;
|
||||
stepContext.Object.Result = TaskResult.Succeeded;
|
||||
}
|
||||
stepContext.Object.UpdateGlobalStepsContext();
|
||||
});
|
||||
var trace = hc.GetTrace();
|
||||
stepContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { trace.Info($"[{tag}]{message}"); });
|
||||
stepContext.Object.Result = result;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<NoWarn>NU1701;NU1603;NU1603;xUnit2013;</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
13
src/dev.sh
13
src/dev.sh
@@ -22,7 +22,7 @@ DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
|
||||
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
||||
PACKAGE_TRIMS_DIR="$SCRIPT_DIR/../_package_trims"
|
||||
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
||||
DOTNETSDK_VERSION="6.0.100"
|
||||
DOTNETSDK_VERSION="6.0.300"
|
||||
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
||||
RUNNER_VERSION=$(cat runnerversion)
|
||||
|
||||
@@ -54,6 +54,12 @@ elif [[ "$CURRENT_PLATFORM" == 'linux' ]]; then
|
||||
fi
|
||||
elif [[ "$CURRENT_PLATFORM" == 'darwin' ]]; then
|
||||
RUNTIME_ID='osx-x64'
|
||||
if command -v uname > /dev/null; then
|
||||
CPU_NAME=$(uname -m)
|
||||
case $CPU_NAME in
|
||||
arm64) RUNTIME_ID="osx-arm64";;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "$DEV_TARGET_RUNTIME" ]]; then
|
||||
@@ -63,7 +69,7 @@ fi
|
||||
# Make sure current platform support publish the dotnet runtime
|
||||
# Windows can publish win-x86/x64
|
||||
# Linux can publish linux-x64/arm/arm64
|
||||
# OSX can publish osx-x64
|
||||
# OSX can publish osx-x64/arm64
|
||||
if [[ "$CURRENT_PLATFORM" == 'windows' ]]; then
|
||||
if [[ ("$RUNTIME_ID" != 'win-x86') && ("$RUNTIME_ID" != 'win-x64') ]]; then
|
||||
echo "Failed: Can't build $RUNTIME_ID package $CURRENT_PLATFORM" >&2
|
||||
@@ -75,7 +81,7 @@ elif [[ "$CURRENT_PLATFORM" == 'linux' ]]; then
|
||||
exit 1
|
||||
fi
|
||||
elif [[ "$CURRENT_PLATFORM" == 'darwin' ]]; then
|
||||
if [[ ("$RUNTIME_ID" != 'osx-x64') ]]; then
|
||||
if [[ ("$RUNTIME_ID" != 'osx-x64') && ("$RUNTIME_ID" != 'osx-arm64') ]]; then
|
||||
echo "Failed: Can't build $RUNTIME_ID package $CURRENT_PLATFORM" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -128,6 +134,7 @@ function layout ()
|
||||
chmod +x "${LAYOUT_DIR}/bin/Runner.Worker"
|
||||
chmod +x "${LAYOUT_DIR}/bin/Runner.PluginHost"
|
||||
chmod +x "${LAYOUT_DIR}/bin/installdependencies.sh"
|
||||
chmod +x "${LAYOUT_DIR}/safe_sleep.sh"
|
||||
fi
|
||||
|
||||
heading "Setup externals folder for $RUNTIME_ID runner's layout"
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
<Target Name="Test" DependsOnTargets="GenerateConstant">
|
||||
<Exec Command="dotnet build Test/Test.csproj -c $(BUILDCONFIG) /p:PackageRuntime=$(PackageRuntime)" ConsoleToMSBuild="true" />
|
||||
<Exec Command="dotnet test Test/Test.csproj --no-build --logger:trx" ConsoleToMSBuild="true" />
|
||||
<Exec Command="dotnet test Test/Test.csproj -c $(BUILDCONFIG) --no-build --logger:trx" ConsoleToMSBuild="true" />
|
||||
</Target>
|
||||
|
||||
<Target Name="Layout" DependsOnTargets="Clean;Build">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "6.0.100"
|
||||
"version": "6.0.300"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.288.0
|
||||
2.292.0
|
||||
|
||||
Reference in New Issue
Block a user