mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:36:23 +00:00
Compare commits
27 Commits
v2.326.0
...
salmanmkc/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
163adda80b | ||
|
|
f80ce89387 | ||
|
|
65dfa460ba | ||
|
|
80ee51f164 | ||
|
|
c95883f28e | ||
|
|
6e940643a9 | ||
|
|
629f2384a4 | ||
|
|
c3bf70becb | ||
|
|
8b65f5f9df | ||
|
|
5f1efec208 | ||
|
|
20d82ad357 | ||
|
|
0ebdf9e83d | ||
|
|
6543bf206b | ||
|
|
a942627965 | ||
|
|
83539166c9 | ||
|
|
1c1e8bfd18 | ||
|
|
59177fa379 | ||
|
|
2d7635a7f0 | ||
|
|
0203cf24d3 | ||
|
|
5e74a4d8e4 | ||
|
|
6ca97eeb88 | ||
|
|
8a9b96806d | ||
|
|
dc9cf684c9 | ||
|
|
c765c990b9 | ||
|
|
ed48ddd08c | ||
|
|
a1e6ad8d2e | ||
|
|
14856e63bc |
@@ -4,7 +4,7 @@
|
|||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
||||||
"ghcr.io/devcontainers/features/dotnet": {
|
"ghcr.io/devcontainers/features/dotnet": {
|
||||||
"version": "8.0.411"
|
"version": "8.0.413"
|
||||||
},
|
},
|
||||||
"ghcr.io/devcontainers/features/node:1": {
|
"ghcr.io/devcontainers/features/node:1": {
|
||||||
"version": "20"
|
"version": "20"
|
||||||
|
|||||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
|||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
# Build runner layout
|
# Build runner layout
|
||||||
- name: Build & Layout Release
|
- name: Build & Layout Release
|
||||||
|
|||||||
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
|
|||||||
4
.github/workflows/docker-buildx-upgrade.yml
vendored
4
.github/workflows/docker-buildx-upgrade.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
BUILDX_CURRENT_VERSION: ${{ steps.check_buildx_version.outputs.CURRENT_VERSION }}
|
BUILDX_CURRENT_VERSION: ${{ steps.check_buildx_version.outputs.CURRENT_VERSION }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Check Docker version
|
- name: Check Docker version
|
||||||
id: check_docker_version
|
id: check_docker_version
|
||||||
@@ -89,7 +89,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Update Docker version
|
- name: Update Docker version
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
4
.github/workflows/dotnet-upgrade.yml
vendored
4
.github/workflows/dotnet-upgrade.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
DOTNET_CURRENT_MAJOR_MINOR_VERSION: ${{ steps.fetch_current_version.outputs.DOTNET_CURRENT_MAJOR_MINOR_VERSION }}
|
DOTNET_CURRENT_MAJOR_MINOR_VERSION: ${{ steps.fetch_current_version.outputs.DOTNET_CURRENT_MAJOR_MINOR_VERSION }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
- name: Get current major minor version
|
- name: Get current major minor version
|
||||||
id: fetch_current_version
|
id: fetch_current_version
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -89,7 +89,7 @@ jobs:
|
|||||||
if: ${{ needs.dotnet-update.outputs.SHOULD_UPDATE == 1 && needs.dotnet-update.outputs.BRANCH_EXISTS == 0 }}
|
if: ${{ needs.dotnet-update.outputs.SHOULD_UPDATE == 1 && needs.dotnet-update.outputs.BRANCH_EXISTS == 0 }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: feature/dotnetsdk-upgrade/${{ needs.dotnet-update.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}
|
ref: feature/dotnetsdk-upgrade/${{ needs.dotnet-update.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
|
|||||||
24
.github/workflows/release.yml
vendored
24
.github/workflows/release.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
if: startsWith(github.ref, 'refs/heads/releases/') || github.ref == 'refs/heads/main'
|
if: startsWith(github.ref, 'refs/heads/releases/') || github.ref == 'refs/heads/main'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
# Make sure ./releaseVersion match ./src/runnerversion
|
# Make sure ./releaseVersion match ./src/runnerversion
|
||||||
# Query GitHub release ensure version is not used
|
# Query GitHub release ensure version is not used
|
||||||
@@ -86,7 +86,7 @@ jobs:
|
|||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
# Build runner layout
|
# Build runner layout
|
||||||
- name: Build & Layout Release
|
- name: Build & Layout Release
|
||||||
@@ -129,41 +129,41 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
# Download runner package tar.gz/zip produced by 'build' job
|
# Download runner package tar.gz/zip produced by 'build' job
|
||||||
- name: Download Artifact (win-x64)
|
- name: Download Artifact (win-x64)
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: runner-packages-win-x64
|
name: runner-packages-win-x64
|
||||||
path: ./
|
path: ./
|
||||||
- name: Download Artifact (win-arm64)
|
- name: Download Artifact (win-arm64)
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: runner-packages-win-arm64
|
name: runner-packages-win-arm64
|
||||||
path: ./
|
path: ./
|
||||||
- name: Download Artifact (osx-x64)
|
- name: Download Artifact (osx-x64)
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: runner-packages-osx-x64
|
name: runner-packages-osx-x64
|
||||||
path: ./
|
path: ./
|
||||||
- name: Download Artifact (osx-arm64)
|
- name: Download Artifact (osx-arm64)
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: runner-packages-osx-arm64
|
name: runner-packages-osx-arm64
|
||||||
path: ./
|
path: ./
|
||||||
- name: Download Artifact (linux-x64)
|
- name: Download Artifact (linux-x64)
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: runner-packages-linux-x64
|
name: runner-packages-linux-x64
|
||||||
path: ./
|
path: ./
|
||||||
- name: Download Artifact (linux-arm)
|
- name: Download Artifact (linux-arm)
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: runner-packages-linux-arm
|
name: runner-packages-linux-arm
|
||||||
path: ./
|
path: ./
|
||||||
- name: Download Artifact (linux-arm64)
|
- name: Download Artifact (linux-arm64)
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: runner-packages-linux-arm64
|
name: runner-packages-linux-arm64
|
||||||
path: ./
|
path: ./
|
||||||
@@ -296,7 +296,7 @@ jobs:
|
|||||||
IMAGE_NAME: ${{ github.repository_owner }}/actions-runner
|
IMAGE_NAME: ${{ github.repository_owner }}/actions-runner
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Compute image version
|
- name: Compute image version
|
||||||
id: image
|
id: image
|
||||||
@@ -338,7 +338,7 @@ jobs:
|
|||||||
org.opencontainers.image.licenses=MIT
|
org.opencontainers.image.licenses=MIT
|
||||||
|
|
||||||
- name: Generate attestation
|
- name: Generate attestation
|
||||||
uses: actions/attest-build-provenance@v2
|
uses: actions/attest-build-provenance@v3
|
||||||
with:
|
with:
|
||||||
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
subject-digest: ${{ steps.build-and-push.outputs.digest }}
|
subject-digest: ${{ steps.build-and-push.outputs.digest }}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
|
|
||||||
Make sure the built-in node.js has access to GitHub.com or GitHub Enterprise Server.
|
Make sure the built-in node.js has access to GitHub.com or GitHub Enterprise Server.
|
||||||
|
|
||||||
The runner carries its own copy of node.js executable under `<runner_root>/externals/node20/`.
|
The runner carries its own copies of node.js executables under `<runner_root>/externals/node20/` and `<runner_root>/externals/node24/`.
|
||||||
|
|
||||||
All javascript base Actions will get executed by the built-in `node` at `<runner_root>/externals/node20/`.
|
All javascript base Actions will get executed by the built-in `node` at either `<runner_root>/externals/node20/` or `<runner_root>/externals/node24/` depending on the version specified in the action's metadata.
|
||||||
|
|
||||||
> Not the `node` from `$PATH`
|
> Not the `node` from `$PATH`
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
## Supported Distributions and Versions
|
## Supported Distributions and Versions
|
||||||
|
|
||||||
Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#linux)."
|
Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/reference/runners/self-hosted-runners#linux)."
|
||||||
|
|
||||||
## Install .Net Core 3.x Linux Dependencies
|
## Install .Net Core 3.x Linux Dependencies
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#macos)."
|
Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/reference/runners/self-hosted-runners#macos)."
|
||||||
|
|
||||||
## [More .Net Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/macos-prerequisites?tabs=netcore30)
|
## [More .Net Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/macos-prerequisites?tabs=netcore30)
|
||||||
|
|||||||
@@ -2,6 +2,6 @@
|
|||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#windows)."
|
Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/reference/runners/self-hosted-runners#windows)."
|
||||||
|
|
||||||
## [More .NET Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore30)
|
## [More .NET Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore30)
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ ARG TARGETOS
|
|||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG RUNNER_VERSION
|
ARG RUNNER_VERSION
|
||||||
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.7.0
|
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.7.0
|
||||||
ARG DOCKER_VERSION=28.3.0
|
ARG DOCKER_VERSION=28.3.3
|
||||||
ARG BUILDX_VERSION=0.25.0
|
ARG BUILDX_VERSION=0.27.0
|
||||||
|
|
||||||
RUN apt update -y && apt install curl unzip -y
|
RUN apt update -y && apt install curl unzip -y
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
## What's Changed
|
## What's Changed
|
||||||
* runner timestamps invariant by @GhadimiR in https://github.com/actions/runner/pull/3888
|
* Update Docker to v28.3.2 and Buildx to v0.26.1 by @github-actions[bot] in https://github.com/actions/runner/pull/3953
|
||||||
* Update README.md by @nebuk89 in https://github.com/actions/runner/pull/3898
|
* Fix if statement structure in update script and variable reference by @salmanmkc in https://github.com/actions/runner/pull/3956
|
||||||
* Update dotnet sdk to latest version @8.0.411 by @github-actions in https://github.com/actions/runner/pull/3911
|
* Add V2 flow for runner deletion by @Samirat in https://github.com/actions/runner/pull/3954
|
||||||
* Update Docker to v28.2.2 and Buildx to v0.25.0 by @github-actions in https://github.com/actions/runner/pull/3918
|
* Node 20 -> Node 24 migration feature flagging, opt-in and opt-out environment variables by @salmanmkc in https://github.com/actions/runner/pull/3948
|
||||||
* Bump windows service app to dotnet 4.7 by @TingluoHuang in https://github.com/actions/runner/pull/3926
|
* Update Node20 and Node24 to latest by @djs-intel in https://github.com/actions/runner/pull/3972
|
||||||
* Upgrade node.js to latest version. by @TingluoHuang in https://github.com/actions/runner/pull/3935
|
* Redirect supported OS doc section to current public Docs location by @corycalahan in https://github.com/actions/runner/pull/3979
|
||||||
|
* Bump Microsoft.NET.Test.Sdk from 17.13.0 to 17.14.1 by @dependabot[bot] in https://github.com/actions/runner/pull/3975
|
||||||
|
* Bump Azure.Storage.Blobs from 12.24.0 to 12.25.0 by @dependabot[bot] in https://github.com/actions/runner/pull/3974
|
||||||
|
* Bump actions/download-artifact from 4 to 5 by @dependabot[bot] in https://github.com/actions/runner/pull/3973
|
||||||
|
* Bump actions/checkout from 4 to 5 by @dependabot[bot] in https://github.com/actions/runner/pull/3982
|
||||||
|
|
||||||
## New Contributors
|
## New Contributors
|
||||||
* @nebuk89 made their first contribution in https://github.com/actions/runner/pull/3898
|
* @Samirat made their first contribution in https://github.com/actions/runner/pull/3954
|
||||||
|
* @djs-intel made their first contribution in https://github.com/actions/runner/pull/3972
|
||||||
|
|
||||||
**Full Changelog**: https://github.com/actions/runner/compare/v2.325.0...v2.326.0
|
**Full Changelog**: https://github.com/actions/runner/compare/v2.327.1...v2.328.0
|
||||||
|
|
||||||
_Note: Actions Runner follows a progressive release policy, so the latest release might not be available to your enterprise, organization, or repository yet.
|
_Note: Actions Runner follows a progressive release policy, so the latest release might not be available to your enterprise, organization, or repository yet.
|
||||||
To confirm which version of the Actions Runner you should expect, please view the download instructions for your enterprise, organization, or repository.
|
To confirm which version of the Actions Runner you should expect, please view the download instructions for your enterprise, organization, or repository.
|
||||||
|
|||||||
1070
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
1070
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -36,13 +36,13 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.6.2",
|
"@types/node": "^20.6.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.7.2",
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||||
"@typescript-eslint/parser": "^6.7.2",
|
"@typescript-eslint/parser": "^6.7.2",
|
||||||
"@vercel/ncc": "^0.38.0",
|
"@vercel/ncc": "^0.38.3",
|
||||||
"eslint": "^8.47.0",
|
"eslint": "^8.47.0",
|
||||||
"eslint-plugin-github": "^4.10.0",
|
"eslint-plugin-github": "^4.10.2",
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
"husky": "^8.0.3",
|
"husky": "^9.1.7",
|
||||||
"lint-staged": "^15.5.0",
|
"lint-staged": "^15.5.0",
|
||||||
"prettier": "^3.0.3",
|
"prettier": "^3.0.3",
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.2.2"
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ NODE_URL=https://nodejs.org/dist
|
|||||||
NODE_ALPINE_URL=https://github.com/actions/alpine_nodejs/releases/download
|
NODE_ALPINE_URL=https://github.com/actions/alpine_nodejs/releases/download
|
||||||
# When you update Node versions you must also create a new release of alpine_nodejs at that updated version.
|
# When you update Node versions you must also create a new release of alpine_nodejs at that updated version.
|
||||||
# Follow the instructions here: https://github.com/actions/alpine_nodejs?tab=readme-ov-file#getting-started
|
# Follow the instructions here: https://github.com/actions/alpine_nodejs?tab=readme-ov-file#getting-started
|
||||||
NODE20_VERSION="20.19.3"
|
NODE20_VERSION="20.19.4"
|
||||||
|
NODE24_VERSION="24.5.0"
|
||||||
|
|
||||||
get_abs_path() {
|
get_abs_path() {
|
||||||
# exploits the fact that pwd will print abs path when no args
|
# exploits the fact that pwd will print abs path when no args
|
||||||
@@ -139,6 +140,8 @@ function acquireExternalTool() {
|
|||||||
if [[ "$PACKAGERUNTIME" == "win-x64" || "$PACKAGERUNTIME" == "win-x86" ]]; then
|
if [[ "$PACKAGERUNTIME" == "win-x64" || "$PACKAGERUNTIME" == "win-x86" ]]; then
|
||||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.exe" node20/bin
|
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.exe" node20/bin
|
||||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.lib" node20/bin
|
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.lib" node20/bin
|
||||||
|
acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/$PACKAGERUNTIME/node.exe" node24/bin
|
||||||
|
acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/$PACKAGERUNTIME/node.lib" node24/bin
|
||||||
if [[ "$PRECACHE" != "" ]]; then
|
if [[ "$PRECACHE" != "" ]]; then
|
||||||
acquireExternalTool "https://github.com/microsoft/vswhere/releases/download/2.6.7/vswhere.exe" vswhere
|
acquireExternalTool "https://github.com/microsoft/vswhere/releases/download/2.6.7/vswhere.exe" vswhere
|
||||||
fi
|
fi
|
||||||
@@ -149,6 +152,8 @@ if [[ "$PACKAGERUNTIME" == "win-arm64" ]]; then
|
|||||||
# todo: replace these with official release when available
|
# todo: replace these with official release when available
|
||||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.exe" node20/bin
|
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.exe" node20/bin
|
||||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.lib" node20/bin
|
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.lib" node20/bin
|
||||||
|
acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/$PACKAGERUNTIME/node.exe" node24/bin
|
||||||
|
acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/$PACKAGERUNTIME/node.lib" node24/bin
|
||||||
if [[ "$PRECACHE" != "" ]]; then
|
if [[ "$PRECACHE" != "" ]]; then
|
||||||
acquireExternalTool "https://github.com/microsoft/vswhere/releases/download/2.6.7/vswhere.exe" vswhere
|
acquireExternalTool "https://github.com/microsoft/vswhere/releases/download/2.6.7/vswhere.exe" vswhere
|
||||||
fi
|
fi
|
||||||
@@ -157,21 +162,26 @@ fi
|
|||||||
# Download the external tools only for OSX.
|
# Download the external tools only for OSX.
|
||||||
if [[ "$PACKAGERUNTIME" == "osx-x64" ]]; then
|
if [[ "$PACKAGERUNTIME" == "osx-x64" ]]; then
|
||||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-darwin-x64.tar.gz" node20 fix_nested_dir
|
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-darwin-x64.tar.gz" node20 fix_nested_dir
|
||||||
|
acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/node-v${NODE24_VERSION}-darwin-x64.tar.gz" node24 fix_nested_dir
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$PACKAGERUNTIME" == "osx-arm64" ]]; then
|
if [[ "$PACKAGERUNTIME" == "osx-arm64" ]]; then
|
||||||
# node.js v12 doesn't support macOS on arm64.
|
# node.js v12 doesn't support macOS on arm64.
|
||||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-darwin-arm64.tar.gz" node20 fix_nested_dir
|
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-darwin-arm64.tar.gz" node20 fix_nested_dir
|
||||||
|
acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/node-v${NODE24_VERSION}-darwin-arm64.tar.gz" node24 fix_nested_dir
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Download the external tools for Linux PACKAGERUNTIMEs.
|
# Download the external tools for Linux PACKAGERUNTIMEs.
|
||||||
if [[ "$PACKAGERUNTIME" == "linux-x64" ]]; then
|
if [[ "$PACKAGERUNTIME" == "linux-x64" ]]; then
|
||||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-linux-x64.tar.gz" node20 fix_nested_dir
|
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-linux-x64.tar.gz" node20 fix_nested_dir
|
||||||
acquireExternalTool "$NODE_ALPINE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-alpine-x64.tar.gz" node20_alpine
|
acquireExternalTool "$NODE_ALPINE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-alpine-x64.tar.gz" node20_alpine
|
||||||
|
acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/node-v${NODE24_VERSION}-linux-x64.tar.gz" node24 fix_nested_dir
|
||||||
|
acquireExternalTool "$NODE_ALPINE_URL/v${NODE24_VERSION}/node-v${NODE24_VERSION}-alpine-x64.tar.gz" node24_alpine
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$PACKAGERUNTIME" == "linux-arm64" ]]; then
|
if [[ "$PACKAGERUNTIME" == "linux-arm64" ]]; then
|
||||||
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-linux-arm64.tar.gz" node20 fix_nested_dir
|
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-linux-arm64.tar.gz" node20 fix_nested_dir
|
||||||
|
acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/node-v${NODE24_VERSION}-linux-arm64.tar.gz" node24 fix_nested_dir
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$PACKAGERUNTIME" == "linux-arm" ]]; then
|
if [[ "$PACKAGERUNTIME" == "linux-arm" ]]; then
|
||||||
|
|||||||
@@ -131,7 +131,32 @@ then
|
|||||||
command -v dnf
|
command -v dnf
|
||||||
if [ $? -eq 0 ]
|
if [ $? -eq 0 ]
|
||||||
then
|
then
|
||||||
dnf install -y lttng-ust openssl-libs krb5-libs zlib libicu
|
dnf_with_fallbacks() {
|
||||||
|
dnf install -y $1
|
||||||
|
fail=$?
|
||||||
|
if [ $fail -eq 0 ]
|
||||||
|
then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ $fail -ne 0 ]
|
||||||
|
then
|
||||||
|
shift
|
||||||
|
if [ -n "$1" ]
|
||||||
|
then
|
||||||
|
dnf_with_fallbacks "$@"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
dnf_with_fallbacks lttng-ust1 lttng-ust0
|
||||||
|
if [ $? -ne 0 ]
|
||||||
|
then
|
||||||
|
echo "'dnf' failed with exit code '$?'"
|
||||||
|
print_errormessage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
dnf install -y openssl-libs krb5-libs zlib libicu
|
||||||
if [ $? -ne 0 ]
|
if [ $? -ne 0 ]
|
||||||
then
|
then
|
||||||
echo "'dnf' failed with exit code '$?'"
|
echo "'dnf' failed with exit code '$?'"
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ fi
|
|||||||
# fix upgrade issue with macOS when running as a service
|
# fix upgrade issue with macOS when running as a service
|
||||||
attemptedtargetedfix=0
|
attemptedtargetedfix=0
|
||||||
currentplatform=$(uname | awk '{print tolower($0)}')
|
currentplatform=$(uname | awk '{print tolower($0)}')
|
||||||
if [[ "$currentplatform" == 'darwin' && restartinteractiverunner -eq 0 ]]; then
|
if [[ "$currentplatform" == 'darwin' && $restartinteractiverunner -eq 0 ]]; then
|
||||||
# We needed a fix for https://github.com/actions/runner/issues/743
|
# We needed a fix for https://github.com/actions/runner/issues/743
|
||||||
# We will recreate the ./externals/nodeXY/bin/node of the past runner version that launched the runnerlistener service
|
# We will recreate the ./externals/nodeXY/bin/node of the past runner version that launched the runnerlistener service
|
||||||
# Otherwise mac gatekeeper kills the processes we spawn on creation as we are running a process with no backing file
|
# Otherwise mac gatekeeper kills the processes we spawn on creation as we are running a process with no backing file
|
||||||
@@ -135,16 +135,22 @@ if [[ "$currentplatform" == 'darwin' && restartinteractiverunner -eq 0 ]]; then
|
|||||||
then
|
then
|
||||||
# inspect the open file handles to find the node process
|
# inspect the open file handles to find the node process
|
||||||
# we can't actually inspect the process using ps because it uses relative paths and doesn't follow symlinks
|
# we can't actually inspect the process using ps because it uses relative paths and doesn't follow symlinks
|
||||||
nodever="node20"
|
# Try finding node24 first, then fallback to earlier versions if needed
|
||||||
|
nodever="node24"
|
||||||
path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-)
|
path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-)
|
||||||
if [[ $? -ne 0 || -z "$path" ]] # Fallback if RunnerService.js was started with node16
|
if [[ $? -ne 0 || -z "$path" ]] # Fallback if RunnerService.js was started with node20
|
||||||
then
|
then
|
||||||
nodever="node16"
|
nodever="node20"
|
||||||
path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-)
|
path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-)
|
||||||
if [[ $? -ne 0 || -z "$path" ]] # Fallback if RunnerService.js was started with node12
|
if [[ $? -ne 0 || -z "$path" ]] # Fallback if RunnerService.js was started with node16
|
||||||
then
|
then
|
||||||
nodever="node12"
|
nodever="node16"
|
||||||
path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-)
|
path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-)
|
||||||
|
if [[ $? -ne 0 || -z "$path" ]] # Fallback if RunnerService.js was started with node12
|
||||||
|
then
|
||||||
|
nodever="node12"
|
||||||
|
path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-)
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [[ $? -eq 0 && -n "$path" ]]
|
if [[ $? -eq 0 && -n "$path" ]]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
SECONDS=0
|
SECONDS=0
|
||||||
while [[ $SECONDS != $1 ]]; do
|
while [[ $SECONDS -lt $1 ]]; do
|
||||||
:
|
:
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
Task<TaskAgentMessage> GetRunnerMessageAsync(Guid? sessionId, TaskAgentStatus status, string version, string os, string architecture, bool disableUpdate, CancellationToken token);
|
Task<TaskAgentMessage> GetRunnerMessageAsync(Guid? sessionId, TaskAgentStatus status, string version, string os, string architecture, bool disableUpdate, CancellationToken token);
|
||||||
|
|
||||||
|
Task AcknowledgeRunnerRequestAsync(string runnerRequestId, Guid? sessionId, TaskAgentStatus status, string version, string os, string architecture, CancellationToken token);
|
||||||
|
|
||||||
Task UpdateConnectionIfNeeded(Uri serverUri, VssCredentials credentials);
|
Task UpdateConnectionIfNeeded(Uri serverUri, VssCredentials credentials);
|
||||||
|
|
||||||
Task ForceRefreshConnection(VssCredentials credentials);
|
Task ForceRefreshConnection(VssCredentials credentials);
|
||||||
@@ -67,10 +69,17 @@ namespace GitHub.Runner.Common
|
|||||||
var brokerSession = RetryRequest<TaskAgentMessage>(
|
var brokerSession = RetryRequest<TaskAgentMessage>(
|
||||||
async () => await _brokerHttpClient.GetRunnerMessageAsync(sessionId, version, status, os, architecture, disableUpdate, cancellationToken), cancellationToken, shouldRetry: ShouldRetryException);
|
async () => await _brokerHttpClient.GetRunnerMessageAsync(sessionId, version, status, os, architecture, disableUpdate, cancellationToken), cancellationToken, shouldRetry: ShouldRetryException);
|
||||||
|
|
||||||
|
|
||||||
return brokerSession;
|
return brokerSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task AcknowledgeRunnerRequestAsync(string runnerRequestId, Guid? sessionId, TaskAgentStatus status, string version, string os, string architecture, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
CheckConnection();
|
||||||
|
|
||||||
|
// No retries
|
||||||
|
await _brokerHttpClient.AcknowledgeRunnerRequestAsync(runnerRequestId, sessionId, version, status, os, architecture, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task DeleteSessionAsync(CancellationToken cancellationToken)
|
public async Task DeleteSessionAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
CheckConnection();
|
CheckConnection();
|
||||||
|
|||||||
@@ -170,6 +170,22 @@ namespace GitHub.Runner.Common
|
|||||||
public static readonly string AddCheckRunIdToJobContext = "actions_add_check_run_id_to_job_context";
|
public static readonly string AddCheckRunIdToJobContext = "actions_add_check_run_id_to_job_context";
|
||||||
public static readonly string DisplayHelpfulActionsDownloadErrors = "actions_display_helpful_actions_download_errors";
|
public static readonly string DisplayHelpfulActionsDownloadErrors = "actions_display_helpful_actions_download_errors";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Node version migration related constants
|
||||||
|
public static class NodeMigration
|
||||||
|
{
|
||||||
|
// Node versions
|
||||||
|
public static readonly string Node20 = "node20";
|
||||||
|
public static readonly string Node24 = "node24";
|
||||||
|
|
||||||
|
// Environment variables for controlling node version selection
|
||||||
|
public static readonly string ForceNode24Variable = "FORCE_JAVASCRIPT_ACTIONS_TO_NODE24";
|
||||||
|
public static readonly string AllowUnsecureNodeVersionVariable = "ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION";
|
||||||
|
|
||||||
|
// Feature flags for controlling the migration phases
|
||||||
|
public static readonly string UseNode24ByDefaultFlag = "actions.runner.usenode24bydefault";
|
||||||
|
public static readonly string RequireNode24Flag = "actions.runner.requirenode24";
|
||||||
|
}
|
||||||
|
|
||||||
public static readonly string InternalTelemetryIssueDataKey = "_internal_telemetry";
|
public static readonly string InternalTelemetryIssueDataKey = "_internal_telemetry";
|
||||||
public static readonly Guid TelemetryRecordId = new Guid("11111111-1111-1111-1111-111111111111");
|
public static readonly Guid TelemetryRecordId = new Guid("11111111-1111-1111-1111-111111111111");
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ using System.Threading.Tasks;
|
|||||||
using GitHub.DistributedTask.Logging;
|
using GitHub.DistributedTask.Logging;
|
||||||
using GitHub.Runner.Common.Util;
|
using GitHub.Runner.Common.Util;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
|
using GitHub.Services.WebApi.Jwt;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common
|
namespace GitHub.Runner.Common
|
||||||
{
|
{
|
||||||
@@ -306,6 +307,36 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
_userAgents.Add(new ProductInfoHeaderValue("ClientId", clientId));
|
_userAgents.Add(new ProductInfoHeaderValue("ClientId", clientId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for Hosted runner, we can pull orchestrationId from JWT claims of the runner listening token.
|
||||||
|
if (credData != null &&
|
||||||
|
credData.Scheme == Constants.Configuration.OAuthAccessToken &&
|
||||||
|
credData.Data.TryGetValue(Constants.Runner.CommandLine.Args.Token, out var accessToken) &&
|
||||||
|
!string.IsNullOrEmpty(accessToken))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var jwt = JsonWebToken.Create(accessToken);
|
||||||
|
var claims = jwt.ExtractClaims();
|
||||||
|
var orchestrationId = claims.FirstOrDefault(x => string.Equals(x.Type, "orch_id", StringComparison.OrdinalIgnoreCase))?.Value;
|
||||||
|
if (string.IsNullOrEmpty(orchestrationId))
|
||||||
|
{
|
||||||
|
// fallback to orchid for C# actions-service
|
||||||
|
orchestrationId = claims.FirstOrDefault(x => string.Equals(x.Type, "orchid", StringComparison.OrdinalIgnoreCase))?.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(orchestrationId))
|
||||||
|
{
|
||||||
|
_trace.Info($"Pull OrchestrationId {orchestrationId} from runner JWT claims");
|
||||||
|
_userAgents.Insert(0, new ProductInfoHeaderValue("OrchestrationId", orchestrationId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_trace.Error("Fail to extract OrchestrationId from runner JWT claims");
|
||||||
|
_trace.Error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var runnerFile = GetConfigFile(WellKnownConfigFile.Runner);
|
var runnerFile = GetConfigFile(WellKnownConfigFile.Runner);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
Task<DistributedTask.WebApi.Runner> AddRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey);
|
Task<DistributedTask.WebApi.Runner> AddRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey);
|
||||||
Task<DistributedTask.WebApi.Runner> ReplaceRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey);
|
Task<DistributedTask.WebApi.Runner> ReplaceRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey);
|
||||||
|
Task DeleteRunnerAsync(string githubUrl, string githubToken, ulong runnerId);
|
||||||
Task<List<TaskAgentPool>> GetRunnerGroupsAsync(string githubUrl, string githubToken);
|
Task<List<TaskAgentPool>> GetRunnerGroupsAsync(string githubUrl, string githubToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,117 +44,15 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
public async Task<List<TaskAgent>> GetRunnerByNameAsync(string githubUrl, string githubToken, string agentName)
|
public async Task<List<TaskAgent>> GetRunnerByNameAsync(string githubUrl, string githubToken, string agentName)
|
||||||
{
|
{
|
||||||
var githubApiUrl = "";
|
var githubApiUrl = $"{GetEntityUrl(githubUrl)}/runners?name={Uri.EscapeDataString(agentName)}";
|
||||||
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
|
||||||
var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
var isOrgRunner = path.Length == 1;
|
|
||||||
var isRepoOrEnterpriseRunner = path.Length == 2;
|
|
||||||
var isRepoRunner = isRepoOrEnterpriseRunner && !string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
if (isOrgRunner)
|
|
||||||
{
|
|
||||||
// org runner
|
|
||||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/orgs/{path[0]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/orgs/{path[0]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (isRepoOrEnterpriseRunner)
|
|
||||||
{
|
|
||||||
// Repository runner
|
|
||||||
if (isRepoRunner)
|
|
||||||
{
|
|
||||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/repos/{path[0]}/{path[1]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/repos/{path[0]}/{path[1]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Enterprise runner
|
|
||||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/{path[0]}/{path[1]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/{path[0]}/{path[1]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new ArgumentException($"'{githubUrl}' should point to an org or enterprise.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var runnersList = await RetryRequest<ListRunnersResponse>(githubApiUrl, githubToken, RequestType.Get, 3, "Failed to get agents pools");
|
var runnersList = await RetryRequest<ListRunnersResponse>(githubApiUrl, githubToken, RequestType.Get, 3, "Failed to get agents pools");
|
||||||
|
|
||||||
return runnersList.ToTaskAgents();
|
return runnersList.ToTaskAgents();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<TaskAgentPool>> GetRunnerGroupsAsync(string githubUrl, string githubToken)
|
public async Task<List<TaskAgentPool>> GetRunnerGroupsAsync(string githubUrl, string githubToken)
|
||||||
{
|
{
|
||||||
var githubApiUrl = "";
|
var githubApiUrl = $"{GetEntityUrl(githubUrl)}/runner-groups";
|
||||||
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
|
||||||
var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
var isOrgRunner = path.Length == 1;
|
|
||||||
var isRepoOrEnterpriseRunner = path.Length == 2;
|
|
||||||
var isRepoRunner = isRepoOrEnterpriseRunner && !string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
if (isOrgRunner)
|
|
||||||
{
|
|
||||||
// org runner
|
|
||||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/orgs/{path[0]}/actions/runner-groups";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/orgs/{path[0]}/actions/runner-groups";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (isRepoOrEnterpriseRunner)
|
|
||||||
{
|
|
||||||
// Repository Runner
|
|
||||||
if (isRepoRunner)
|
|
||||||
{
|
|
||||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/repos/{path[0]}/{path[1]}/actions/runner-groups";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/repos/{path[0]}/{path[1]}/actions/runner-groups";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Enterprise Runner
|
|
||||||
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/{path[0]}/{path[1]}/actions/runner-groups";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/{path[0]}/{path[1]}/actions/runner-groups";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new ArgumentException($"'{githubUrl}' should point to an org or enterprise.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var agentPools = await RetryRequest<RunnerGroupList>(githubApiUrl, githubToken, RequestType.Get, 3, "Failed to get agents pools");
|
var agentPools = await RetryRequest<RunnerGroupList>(githubApiUrl, githubToken, RequestType.Get, 3, "Failed to get agents pools");
|
||||||
|
|
||||||
return agentPools?.ToAgentPoolList();
|
return agentPools?.ToAgentPoolList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,6 +103,12 @@ namespace GitHub.Runner.Common
|
|||||||
return await RetryRequest<DistributedTask.WebApi.Runner>(githubApiUrl, githubToken, RequestType.Post, 3, "Failed to add agent", body);
|
return await RetryRequest<DistributedTask.WebApi.Runner>(githubApiUrl, githubToken, RequestType.Post, 3, "Failed to add agent", body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task DeleteRunnerAsync(string githubUrl, string githubToken, ulong runnerId)
|
||||||
|
{
|
||||||
|
var githubApiUrl = $"{GetEntityUrl(githubUrl)}/runners/{runnerId}";
|
||||||
|
await RetryRequest<DistributedTask.WebApi.Runner>(githubApiUrl, githubToken, RequestType.Delete, 3, "Failed to delete agent");
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<T> RetryRequest<T>(string githubApiUrl, string githubToken, RequestType requestType, int maxRetryAttemptsCount = 5, string errorMessage = null, StringContent body = null)
|
private async Task<T> RetryRequest<T>(string githubApiUrl, string githubToken, RequestType requestType, int maxRetryAttemptsCount = 5, string errorMessage = null, StringContent body = null)
|
||||||
{
|
{
|
||||||
int retry = 0;
|
int retry = 0;
|
||||||
@@ -220,13 +125,22 @@ namespace GitHub.Runner.Common
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
HttpResponseMessage response = null;
|
HttpResponseMessage response = null;
|
||||||
if (requestType == RequestType.Get)
|
switch (requestType)
|
||||||
{
|
{
|
||||||
response = await httpClient.GetAsync(githubApiUrl);
|
case RequestType.Get:
|
||||||
}
|
response = await httpClient.GetAsync(githubApiUrl);
|
||||||
else
|
break;
|
||||||
{
|
case RequestType.Post:
|
||||||
response = await httpClient.PostAsync(githubApiUrl, body);
|
response = await httpClient.PostAsync(githubApiUrl, body);
|
||||||
|
break;
|
||||||
|
case RequestType.Patch:
|
||||||
|
response = await httpClient.PatchAsync(githubApiUrl, body);
|
||||||
|
break;
|
||||||
|
case RequestType.Delete:
|
||||||
|
response = await httpClient.DeleteAsync(githubApiUrl);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(requestType), requestType, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response != null)
|
if (response != null)
|
||||||
@@ -261,5 +175,61 @@ namespace GitHub.Runner.Common
|
|||||||
await Task.Delay(backOff);
|
await Task.Delay(backOff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetEntityUrl(string githubUrl)
|
||||||
|
{
|
||||||
|
var githubApiUrl = "";
|
||||||
|
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
||||||
|
var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
var isOrgRunner = path.Length == 1;
|
||||||
|
var isRepoOrEnterpriseRunner = path.Length == 2;
|
||||||
|
var isRepoRunner = isRepoOrEnterpriseRunner && !string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if (isOrgRunner)
|
||||||
|
{
|
||||||
|
// org runner
|
||||||
|
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||||
|
{
|
||||||
|
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/orgs/{path[0]}/actions";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/orgs/{path[0]}/actions";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isRepoOrEnterpriseRunner)
|
||||||
|
{
|
||||||
|
// Repository Runner
|
||||||
|
if (isRepoRunner)
|
||||||
|
{
|
||||||
|
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||||
|
{
|
||||||
|
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/repos/{path[0]}/{path[1]}/actions";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/repos/{path[0]}/{path[1]}/actions";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Enterprise Runner
|
||||||
|
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||||
|
{
|
||||||
|
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/{path[0]}/{path[1]}/actions";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/{path[0]}/{path[1]}/actions";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"'{githubUrl}' should point to an org or enterprise.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return githubApiUrl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
protected async Task RetryRequest(Func<Task> func,
|
protected async Task RetryRequest(Func<Task> func,
|
||||||
CancellationToken cancellationToken,
|
CancellationToken cancellationToken,
|
||||||
int maxRetryAttemptsCount = 5,
|
int maxAttempts = 5,
|
||||||
Func<Exception, bool> shouldRetry = null
|
Func<Exception, bool> shouldRetry = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
@@ -79,31 +79,31 @@ namespace GitHub.Runner.Common
|
|||||||
await func();
|
await func();
|
||||||
return Unit.Value;
|
return Unit.Value;
|
||||||
}
|
}
|
||||||
await RetryRequest<Unit>(wrappedFunc, cancellationToken, maxRetryAttemptsCount, shouldRetry);
|
await RetryRequest<Unit>(wrappedFunc, cancellationToken, maxAttempts, shouldRetry);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async Task<T> RetryRequest<T>(Func<Task<T>> func,
|
protected async Task<T> RetryRequest<T>(Func<Task<T>> func,
|
||||||
CancellationToken cancellationToken,
|
CancellationToken cancellationToken,
|
||||||
int maxRetryAttemptsCount = 5,
|
int maxAttempts = 5,
|
||||||
Func<Exception, bool> shouldRetry = null
|
Func<Exception, bool> shouldRetry = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var retryCount = 0;
|
var attempt = 0;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
retryCount++;
|
attempt++;
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await func();
|
return await func();
|
||||||
}
|
}
|
||||||
// TODO: Add handling of non-retriable exceptions: https://github.com/github/actions-broker/issues/122
|
// TODO: Add handling of non-retriable exceptions: https://github.com/github/actions-broker/issues/122
|
||||||
catch (Exception ex) when (retryCount < maxRetryAttemptsCount && (shouldRetry == null || shouldRetry(ex)))
|
catch (Exception ex) when (attempt < maxAttempts && (shouldRetry == null || shouldRetry(ex)))
|
||||||
{
|
{
|
||||||
Trace.Error("Catch exception during request");
|
Trace.Error("Catch exception during request");
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15));
|
var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15));
|
||||||
Trace.Warning($"Back off {backOff.TotalSeconds} seconds before next retry. {maxRetryAttemptsCount - retryCount} attempt left.");
|
Trace.Warning($"Back off {backOff.TotalSeconds} seconds before next retry. {maxAttempts - attempt} attempt left.");
|
||||||
await Task.Delay(backOff, cancellationToken);
|
await Task.Delay(backOff, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,33 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common.Util
|
namespace GitHub.Runner.Common.Util
|
||||||
{
|
{
|
||||||
public static class NodeUtil
|
public static class NodeUtil
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents details about an environment variable, including its value and source
|
||||||
|
/// </summary>
|
||||||
|
private class EnvironmentVariableInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets whether the value evaluates to true
|
||||||
|
/// </summary>
|
||||||
|
public bool IsTrue { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets whether the value came from the workflow environment
|
||||||
|
/// </summary>
|
||||||
|
public bool FromWorkflow { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets whether the value came from the system environment
|
||||||
|
/// </summary>
|
||||||
|
public bool FromSystem { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
private const string _defaultNodeVersion = "node20";
|
private const string _defaultNodeVersion = "node20";
|
||||||
public static readonly ReadOnlyCollection<string> BuiltInNodeVersions = new(new[] { "node20" });
|
public static readonly ReadOnlyCollection<string> BuiltInNodeVersions = new(new[] { "node20" });
|
||||||
public static string GetInternalNodeVersion()
|
public static string GetInternalNodeVersion()
|
||||||
@@ -18,5 +41,122 @@ namespace GitHub.Runner.Common.Util
|
|||||||
}
|
}
|
||||||
return _defaultNodeVersion;
|
return _defaultNodeVersion;
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Determines the appropriate Node version for Actions to use
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="workflowEnvironment">Optional dictionary containing workflow-level environment variables</param>
|
||||||
|
/// <param name="useNode24ByDefault">Feature flag indicating if Node 24 should be the default</param>
|
||||||
|
/// <param name="requireNode24">Feature flag indicating if Node 24 is required</param>
|
||||||
|
/// <returns>The Node version to use (node20 or node24) and warning message if both env vars are set</returns>
|
||||||
|
public static (string nodeVersion, string warningMessage) DetermineActionsNodeVersion(
|
||||||
|
IDictionary<string, string> workflowEnvironment = null,
|
||||||
|
bool useNode24ByDefault = false,
|
||||||
|
bool requireNode24 = false)
|
||||||
|
{
|
||||||
|
// Phase 3: Always use Node 24 regardless of environment variables
|
||||||
|
if (requireNode24)
|
||||||
|
{
|
||||||
|
return (Constants.Runner.NodeMigration.Node24, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get environment variable details with source information
|
||||||
|
var forceNode24Details = GetEnvironmentVariableDetails(
|
||||||
|
Constants.Runner.NodeMigration.ForceNode24Variable, workflowEnvironment);
|
||||||
|
|
||||||
|
var allowUnsecureNodeDetails = GetEnvironmentVariableDetails(
|
||||||
|
Constants.Runner.NodeMigration.AllowUnsecureNodeVersionVariable, workflowEnvironment);
|
||||||
|
|
||||||
|
bool forceNode24 = forceNode24Details.IsTrue;
|
||||||
|
bool allowUnsecureNode = allowUnsecureNodeDetails.IsTrue;
|
||||||
|
string warningMessage = null;
|
||||||
|
|
||||||
|
// Check if both flags are set from the same source
|
||||||
|
bool bothFromWorkflow = forceNode24Details.IsTrue && allowUnsecureNodeDetails.IsTrue &&
|
||||||
|
forceNode24Details.FromWorkflow && allowUnsecureNodeDetails.FromWorkflow;
|
||||||
|
|
||||||
|
bool bothFromSystem = forceNode24Details.IsTrue && allowUnsecureNodeDetails.IsTrue &&
|
||||||
|
forceNode24Details.FromSystem && allowUnsecureNodeDetails.FromSystem;
|
||||||
|
|
||||||
|
// Handle the case when both are set in the same source
|
||||||
|
if (bothFromWorkflow || bothFromSystem)
|
||||||
|
{
|
||||||
|
string source = bothFromWorkflow ? "workflow" : "system";
|
||||||
|
string defaultVersion = useNode24ByDefault ? Constants.Runner.NodeMigration.Node24 : Constants.Runner.NodeMigration.Node20;
|
||||||
|
warningMessage = $"Both {Constants.Runner.NodeMigration.ForceNode24Variable} and {Constants.Runner.NodeMigration.AllowUnsecureNodeVersionVariable} environment variables are set to true in the {source} environment. This is likely a configuration error. Using the default Node version: {defaultVersion}.";
|
||||||
|
return (defaultVersion, warningMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 2: Node 24 is the default
|
||||||
|
if (useNode24ByDefault)
|
||||||
|
{
|
||||||
|
if (allowUnsecureNode)
|
||||||
|
{
|
||||||
|
return (Constants.Runner.NodeMigration.Node20, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (Constants.Runner.NodeMigration.Node24, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 1: Node 20 is the default
|
||||||
|
if (forceNode24)
|
||||||
|
{
|
||||||
|
return (Constants.Runner.NodeMigration.Node24, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (Constants.Runner.NodeMigration.Node20, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if Node24 is requested but running on ARM32 Linux, and determines if fallback is needed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="preferredVersion">The preferred Node version</param>
|
||||||
|
/// <returns>A tuple containing the adjusted node version and an optional warning message</returns>
|
||||||
|
public static (string nodeVersion, string warningMessage) CheckNodeVersionForLinuxArm32(string preferredVersion)
|
||||||
|
{
|
||||||
|
if (string.Equals(preferredVersion, Constants.Runner.NodeMigration.Node24, StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
Constants.Runner.PlatformArchitecture.Equals(Constants.Architecture.Arm) &&
|
||||||
|
Constants.Runner.Platform.Equals(Constants.OSPlatform.Linux))
|
||||||
|
{
|
||||||
|
return (Constants.Runner.NodeMigration.Node20, "Node 24 is not supported on Linux ARM32 platforms. Falling back to Node 20.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (preferredVersion, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets detailed information about an environment variable from both workflow and system environments
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="variableName">The name of the environment variable</param>
|
||||||
|
/// <param name="workflowEnvironment">Optional dictionary containing workflow-level environment variables</param>
|
||||||
|
/// <returns>An EnvironmentVariableInfo object containing details about the variable from both sources</returns>
|
||||||
|
private static EnvironmentVariableInfo GetEnvironmentVariableDetails(string variableName, IDictionary<string, string> workflowEnvironment)
|
||||||
|
{
|
||||||
|
var info = new EnvironmentVariableInfo();
|
||||||
|
|
||||||
|
// Check workflow environment
|
||||||
|
bool foundInWorkflow = false;
|
||||||
|
string workflowValue = null;
|
||||||
|
|
||||||
|
if (workflowEnvironment != null && workflowEnvironment.TryGetValue(variableName, out workflowValue))
|
||||||
|
{
|
||||||
|
foundInWorkflow = true;
|
||||||
|
info.FromWorkflow = true;
|
||||||
|
info.IsTrue = StringUtil.ConvertToBoolean(workflowValue); // Workflow value takes precedence for the boolean value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check system environment
|
||||||
|
string systemValue = Environment.GetEnvironmentVariable(variableName);
|
||||||
|
bool foundInSystem = !string.IsNullOrEmpty(systemValue);
|
||||||
|
|
||||||
|
info.FromSystem = foundInSystem;
|
||||||
|
|
||||||
|
// If not found in workflow, use system values
|
||||||
|
if (!foundInWorkflow)
|
||||||
|
{
|
||||||
|
info.IsTrue = StringUtil.ConvertToBoolean(systemValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace GitHub.Runner.Listener
|
|||||||
private RunnerSettings _settings;
|
private RunnerSettings _settings;
|
||||||
private ITerminal _term;
|
private ITerminal _term;
|
||||||
private TimeSpan _getNextMessageRetryInterval;
|
private TimeSpan _getNextMessageRetryInterval;
|
||||||
private TaskAgentStatus runnerStatus = TaskAgentStatus.Online;
|
private TaskAgentStatus _runnerStatus = TaskAgentStatus.Online;
|
||||||
private CancellationTokenSource _getMessagesTokenSource;
|
private CancellationTokenSource _getMessagesTokenSource;
|
||||||
private VssCredentials _creds;
|
private VssCredentials _creds;
|
||||||
private VssCredentials _credsV2;
|
private VssCredentials _credsV2;
|
||||||
@@ -258,7 +258,7 @@ namespace GitHub.Runner.Listener
|
|||||||
public void OnJobStatus(object sender, JobStatusEventArgs e)
|
public void OnJobStatus(object sender, JobStatusEventArgs e)
|
||||||
{
|
{
|
||||||
Trace.Info("Received job status event. JobState: {0}", e.Status);
|
Trace.Info("Received job status event. JobState: {0}", e.Status);
|
||||||
runnerStatus = e.Status;
|
_runnerStatus = e.Status;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_getMessagesTokenSource?.Cancel();
|
_getMessagesTokenSource?.Cancel();
|
||||||
@@ -291,7 +291,7 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
|
|
||||||
message = await _brokerServer.GetRunnerMessageAsync(_session.SessionId,
|
message = await _brokerServer.GetRunnerMessageAsync(_session.SessionId,
|
||||||
runnerStatus,
|
_runnerStatus,
|
||||||
BuildConstants.RunnerPackage.Version,
|
BuildConstants.RunnerPackage.Version,
|
||||||
VarUtil.OS,
|
VarUtil.OS,
|
||||||
VarUtil.OSArchitecture,
|
VarUtil.OSArchitecture,
|
||||||
@@ -417,6 +417,21 @@ namespace GitHub.Runner.Listener
|
|||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task AcknowledgeMessageAsync(string runnerRequestId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); // Short timeout
|
||||||
|
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
|
||||||
|
Trace.Info($"Acknowledging runner request '{runnerRequestId}'.");
|
||||||
|
await _brokerServer.AcknowledgeRunnerRequestAsync(
|
||||||
|
runnerRequestId,
|
||||||
|
_session.SessionId,
|
||||||
|
_runnerStatus,
|
||||||
|
BuildConstants.RunnerPackage.Version,
|
||||||
|
VarUtil.OS,
|
||||||
|
VarUtil.OSArchitecture,
|
||||||
|
linkedCts.Token);
|
||||||
|
}
|
||||||
|
|
||||||
private bool IsGetNextMessageExceptionRetriable(Exception ex)
|
private bool IsGetNextMessageExceptionRetriable(Exception ex)
|
||||||
{
|
{
|
||||||
if (ex is TaskAgentNotFoundException ||
|
if (ex is TaskAgentNotFoundException ||
|
||||||
|
|||||||
@@ -537,41 +537,50 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
if (isConfigured && hasCredentials)
|
if (isConfigured && hasCredentials)
|
||||||
{
|
{
|
||||||
RunnerSettings settings = _store.GetSettings();
|
RunnerSettings settings = _store.GetSettings();
|
||||||
var credentialManager = HostContext.GetService<ICredentialManager>();
|
|
||||||
|
|
||||||
// Get the credentials
|
if (settings.UseV2Flow)
|
||||||
VssCredentials creds = null;
|
|
||||||
if (string.IsNullOrEmpty(settings.GitHubUrl))
|
|
||||||
{
|
|
||||||
var credProvider = GetCredentialProvider(command, settings.ServerUrl);
|
|
||||||
creds = credProvider.GetVssCredentials(HostContext, allowAuthUrlV2: false);
|
|
||||||
Trace.Info("legacy vss cred retrieved");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
var deletionToken = await GetRunnerTokenAsync(command, settings.GitHubUrl, "remove");
|
var deletionToken = await GetRunnerTokenAsync(command, settings.GitHubUrl, "remove");
|
||||||
GitHubAuthResult authResult = await GetTenantCredential(settings.GitHubUrl, deletionToken, Constants.RunnerEvent.Remove);
|
await _dotcomServer.DeleteRunnerAsync(settings.GitHubUrl, deletionToken, settings.AgentId);
|
||||||
creds = authResult.ToVssCredentials();
|
|
||||||
Trace.Info("cred retrieved via GitHub auth");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine the service deployment type based on connection data. (Hosted/OnPremises)
|
|
||||||
await _runnerServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
|
||||||
|
|
||||||
var agents = await _runnerServer.GetAgentsAsync(settings.AgentName);
|
|
||||||
Trace.Verbose("Returns {0} agents", agents.Count);
|
|
||||||
TaskAgent agent = agents.FirstOrDefault();
|
|
||||||
if (agent == null)
|
|
||||||
{
|
|
||||||
_term.WriteLine("Does not exist. Skipping " + currentAction);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await _runnerServer.DeleteAgentAsync(settings.AgentId);
|
var credentialManager = HostContext.GetService<ICredentialManager>();
|
||||||
|
|
||||||
_term.WriteLine();
|
// Get the credentials
|
||||||
_term.WriteSuccessMessage("Runner removed successfully");
|
VssCredentials creds = null;
|
||||||
|
if (string.IsNullOrEmpty(settings.GitHubUrl))
|
||||||
|
{
|
||||||
|
var credProvider = GetCredentialProvider(command, settings.ServerUrl);
|
||||||
|
creds = credProvider.GetVssCredentials(HostContext, allowAuthUrlV2: false);
|
||||||
|
Trace.Info("legacy vss cred retrieved");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var deletionToken = await GetRunnerTokenAsync(command, settings.GitHubUrl, "remove");
|
||||||
|
GitHubAuthResult authResult = await GetTenantCredential(settings.GitHubUrl, deletionToken, Constants.RunnerEvent.Remove);
|
||||||
|
creds = authResult.ToVssCredentials();
|
||||||
|
Trace.Info("cred retrieved via GitHub auth");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the service deployment type based on connection data. (Hosted/OnPremises)
|
||||||
|
await _runnerServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
||||||
|
|
||||||
|
var agents = await _runnerServer.GetAgentsAsync(settings.AgentName);
|
||||||
|
Trace.Verbose("Returns {0} agents", agents.Count);
|
||||||
|
TaskAgent agent = agents.FirstOrDefault();
|
||||||
|
if (agent == null)
|
||||||
|
{
|
||||||
|
_term.WriteLine("Does not exist. Skipping " + currentAction);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _runnerServer.DeleteAgentAsync(settings.AgentId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_term.WriteLine();
|
||||||
|
_term.WriteSuccessMessage("Runner removed successfully");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -110,7 +110,12 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
var jwt = JsonWebToken.Create(accessToken);
|
var jwt = JsonWebToken.Create(accessToken);
|
||||||
var claims = jwt.ExtractClaims();
|
var claims = jwt.ExtractClaims();
|
||||||
orchestrationId = claims.FirstOrDefault(x => string.Equals(x.Type, "orchid", StringComparison.OrdinalIgnoreCase))?.Value;
|
orchestrationId = claims.FirstOrDefault(x => string.Equals(x.Type, "orch_id", StringComparison.OrdinalIgnoreCase))?.Value;
|
||||||
|
if (string.IsNullOrEmpty(orchestrationId))
|
||||||
|
{
|
||||||
|
orchestrationId = claims.FirstOrDefault(x => string.Equals(x.Type, "orchid", StringComparison.OrdinalIgnoreCase))?.Value;
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(orchestrationId))
|
if (!string.IsNullOrEmpty(orchestrationId))
|
||||||
{
|
{
|
||||||
Trace.Info($"Pull OrchestrationId {orchestrationId} from JWT claims");
|
Trace.Info($"Pull OrchestrationId {orchestrationId} from JWT claims");
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ namespace GitHub.Runner.Listener
|
|||||||
Task DeleteSessionAsync();
|
Task DeleteSessionAsync();
|
||||||
Task<TaskAgentMessage> GetNextMessageAsync(CancellationToken token);
|
Task<TaskAgentMessage> GetNextMessageAsync(CancellationToken token);
|
||||||
Task DeleteMessageAsync(TaskAgentMessage message);
|
Task DeleteMessageAsync(TaskAgentMessage message);
|
||||||
|
Task AcknowledgeMessageAsync(string runnerRequestId, CancellationToken cancellationToken);
|
||||||
|
|
||||||
Task RefreshListenerTokenAsync();
|
Task RefreshListenerTokenAsync();
|
||||||
void OnJobStatus(object sender, JobStatusEventArgs e);
|
void OnJobStatus(object sender, JobStatusEventArgs e);
|
||||||
@@ -52,7 +53,7 @@ namespace GitHub.Runner.Listener
|
|||||||
private readonly TimeSpan _sessionConflictRetryLimit = TimeSpan.FromMinutes(4);
|
private readonly TimeSpan _sessionConflictRetryLimit = TimeSpan.FromMinutes(4);
|
||||||
private readonly TimeSpan _clockSkewRetryLimit = TimeSpan.FromMinutes(30);
|
private readonly TimeSpan _clockSkewRetryLimit = TimeSpan.FromMinutes(30);
|
||||||
private readonly Dictionary<string, int> _sessionCreationExceptionTracker = new();
|
private readonly Dictionary<string, int> _sessionCreationExceptionTracker = new();
|
||||||
private TaskAgentStatus runnerStatus = TaskAgentStatus.Online;
|
private TaskAgentStatus _runnerStatus = TaskAgentStatus.Online;
|
||||||
private CancellationTokenSource _getMessagesTokenSource;
|
private CancellationTokenSource _getMessagesTokenSource;
|
||||||
private VssCredentials _creds;
|
private VssCredentials _creds;
|
||||||
private VssCredentials _credsV2;
|
private VssCredentials _credsV2;
|
||||||
@@ -217,7 +218,7 @@ namespace GitHub.Runner.Listener
|
|||||||
public void OnJobStatus(object sender, JobStatusEventArgs e)
|
public void OnJobStatus(object sender, JobStatusEventArgs e)
|
||||||
{
|
{
|
||||||
Trace.Info("Received job status event. JobState: {0}", e.Status);
|
Trace.Info("Received job status event. JobState: {0}", e.Status);
|
||||||
runnerStatus = e.Status;
|
_runnerStatus = e.Status;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_getMessagesTokenSource?.Cancel();
|
_getMessagesTokenSource?.Cancel();
|
||||||
@@ -250,7 +251,7 @@ namespace GitHub.Runner.Listener
|
|||||||
message = await _runnerServer.GetAgentMessageAsync(_settings.PoolId,
|
message = await _runnerServer.GetAgentMessageAsync(_settings.PoolId,
|
||||||
_session.SessionId,
|
_session.SessionId,
|
||||||
_lastMessageId,
|
_lastMessageId,
|
||||||
runnerStatus,
|
_runnerStatus,
|
||||||
BuildConstants.RunnerPackage.Version,
|
BuildConstants.RunnerPackage.Version,
|
||||||
VarUtil.OS,
|
VarUtil.OS,
|
||||||
VarUtil.OSArchitecture,
|
VarUtil.OSArchitecture,
|
||||||
@@ -274,7 +275,7 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
|
|
||||||
message = await _brokerServer.GetRunnerMessageAsync(_session.SessionId,
|
message = await _brokerServer.GetRunnerMessageAsync(_session.SessionId,
|
||||||
runnerStatus,
|
_runnerStatus,
|
||||||
BuildConstants.RunnerPackage.Version,
|
BuildConstants.RunnerPackage.Version,
|
||||||
VarUtil.OS,
|
VarUtil.OS,
|
||||||
VarUtil.OSArchitecture,
|
VarUtil.OSArchitecture,
|
||||||
@@ -437,6 +438,21 @@ namespace GitHub.Runner.Listener
|
|||||||
await _brokerServer.ForceRefreshConnection(_credsV2);
|
await _brokerServer.ForceRefreshConnection(_credsV2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task AcknowledgeMessageAsync(string runnerRequestId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); // Short timeout
|
||||||
|
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
|
||||||
|
Trace.Info($"Acknowledging runner request '{runnerRequestId}'.");
|
||||||
|
await _brokerServer.AcknowledgeRunnerRequestAsync(
|
||||||
|
runnerRequestId,
|
||||||
|
_session.SessionId,
|
||||||
|
_runnerStatus,
|
||||||
|
BuildConstants.RunnerPackage.Version,
|
||||||
|
VarUtil.OS,
|
||||||
|
VarUtil.OSArchitecture,
|
||||||
|
linkedCts.Token);
|
||||||
|
}
|
||||||
|
|
||||||
private TaskAgentMessage DecryptMessage(TaskAgentMessage message)
|
private TaskAgentMessage DecryptMessage(TaskAgentMessage message)
|
||||||
{
|
{
|
||||||
if (_session.EncryptionKey == null ||
|
if (_session.EncryptionKey == null ||
|
||||||
|
|||||||
@@ -654,22 +654,42 @@ namespace GitHub.Runner.Listener
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var messageRef = StringUtil.ConvertFromJson<RunnerJobRequestRef>(message.Body);
|
var messageRef = StringUtil.ConvertFromJson<RunnerJobRequestRef>(message.Body);
|
||||||
Pipelines.AgentJobRequestMessage jobRequestMessage = null;
|
|
||||||
|
|
||||||
// Create connection
|
// Acknowledge (best-effort)
|
||||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
if (messageRef.ShouldAcknowledge) // Temporary feature flag
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _listener.AcknowledgeMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Trace.Error($"Best-effort acknowledge failed for request '{messageRef.RunnerRequestId}'");
|
||||||
|
Trace.Error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Pipelines.AgentJobRequestMessage jobRequestMessage = null;
|
||||||
if (string.IsNullOrEmpty(messageRef.RunServiceUrl))
|
if (string.IsNullOrEmpty(messageRef.RunServiceUrl))
|
||||||
{
|
{
|
||||||
|
// Connect
|
||||||
|
var credMgr = HostContext.GetService<ICredentialManager>();
|
||||||
var creds = credMgr.LoadCredentials(allowAuthUrlV2: false);
|
var creds = credMgr.LoadCredentials(allowAuthUrlV2: false);
|
||||||
var actionsRunServer = HostContext.CreateService<IActionsRunServer>();
|
var actionsRunServer = HostContext.CreateService<IActionsRunServer>();
|
||||||
await actionsRunServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
await actionsRunServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
||||||
|
|
||||||
|
// Get job message
|
||||||
jobRequestMessage = await actionsRunServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
jobRequestMessage = await actionsRunServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// Connect
|
||||||
|
var credMgr = HostContext.GetService<ICredentialManager>();
|
||||||
var credsV2 = credMgr.LoadCredentials(allowAuthUrlV2: true);
|
var credsV2 = credMgr.LoadCredentials(allowAuthUrlV2: true);
|
||||||
var runServer = HostContext.CreateService<IRunServer>();
|
var runServer = HostContext.CreateService<IRunServer>();
|
||||||
await runServer.ConnectAsync(new Uri(messageRef.RunServiceUrl), credsV2);
|
await runServer.ConnectAsync(new Uri(messageRef.RunServiceUrl), credsV2);
|
||||||
|
|
||||||
|
// Get job message
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
jobRequestMessage = await runServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageRef.BillingOwnerId, messageQueueLoopTokenSource.Token);
|
jobRequestMessage = await runServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageRef.BillingOwnerId, messageQueueLoopTokenSource.Token);
|
||||||
@@ -698,7 +718,10 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dispatch
|
||||||
jobDispatcher.Run(jobRequestMessage, runOnce);
|
jobDispatcher.Run(jobRequestMessage, runOnce);
|
||||||
|
|
||||||
|
// Run once?
|
||||||
if (runOnce)
|
if (runOnce)
|
||||||
{
|
{
|
||||||
Trace.Info("One time used runner received job message.");
|
Trace.Info("One time used runner received job message.");
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
[DataMember(Name = "runner_request_id")]
|
[DataMember(Name = "runner_request_id")]
|
||||||
public string RunnerRequestId { get; set; }
|
public string RunnerRequestId { get; set; }
|
||||||
|
|
||||||
|
[DataMember(Name = "should_acknowledge")]
|
||||||
|
public bool ShouldAcknowledge { get; set; }
|
||||||
|
|
||||||
[DataMember(Name = "run_service_url")]
|
[DataMember(Name = "run_service_url")]
|
||||||
public string RunServiceUrl { get; set; }
|
public string RunServiceUrl { get; set; }
|
||||||
|
|||||||
@@ -450,7 +450,8 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
else if (string.Equals(usingToken.Value, "node12", StringComparison.OrdinalIgnoreCase) ||
|
else if (string.Equals(usingToken.Value, "node12", StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(usingToken.Value, "node16", StringComparison.OrdinalIgnoreCase) ||
|
string.Equals(usingToken.Value, "node16", StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(usingToken.Value, "node20", StringComparison.OrdinalIgnoreCase))
|
string.Equals(usingToken.Value, "node20", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(usingToken.Value, "node24", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(mainToken?.Value))
|
if (string.IsNullOrEmpty(mainToken?.Value))
|
||||||
{
|
{
|
||||||
@@ -490,7 +491,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new ArgumentOutOfRangeException($"'using: {usingToken.Value}' is not supported, use 'docker', 'node12', 'node16' or 'node20' instead.");
|
throw new ArgumentOutOfRangeException($"'using: {usingToken.Value}' is not supported, use 'docker', 'node12', 'node16', 'node20' or 'node24' instead.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (pluginToken != null)
|
else if (pluginToken != null)
|
||||||
@@ -501,7 +502,7 @@ namespace GitHub.Runner.Worker
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new NotSupportedException("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12', 'node16' or 'node20'.");
|
throw new NotSupportedException("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12', 'node16', 'node20' or 'node24'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ConvertInputs(
|
private void ConvertInputs(
|
||||||
|
|||||||
@@ -58,10 +58,41 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
var nodeData = data as NodeJSActionExecutionData;
|
var nodeData = data as NodeJSActionExecutionData;
|
||||||
|
|
||||||
// With node12 EoL in 04/2022 and node16 EoL in 09/23, we want to execute all JS actions using node20
|
// With node12 EoL in 04/2022 and node16 EoL in 09/23, we want to execute all JS actions using node20
|
||||||
|
// With node20 EoL approaching, we're preparing to migrate to node24
|
||||||
if (string.Equals(nodeData.NodeVersion, "node12", StringComparison.InvariantCultureIgnoreCase) ||
|
if (string.Equals(nodeData.NodeVersion, "node12", StringComparison.InvariantCultureIgnoreCase) ||
|
||||||
string.Equals(nodeData.NodeVersion, "node16", StringComparison.InvariantCultureIgnoreCase))
|
string.Equals(nodeData.NodeVersion, "node16", StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
nodeData.NodeVersion = "node20";
|
nodeData.NodeVersion = Common.Constants.Runner.NodeMigration.Node20;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if node20 was explicitly specified in the action
|
||||||
|
// We don't modify if node24 was explicitly specified
|
||||||
|
if (string.Equals(nodeData.NodeVersion, Constants.Runner.NodeMigration.Node20, StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
bool useNode24ByDefault = executionContext.Global.Variables?.GetBoolean(Constants.Runner.NodeMigration.UseNode24ByDefaultFlag) ?? false;
|
||||||
|
bool requireNode24 = executionContext.Global.Variables?.GetBoolean(Constants.Runner.NodeMigration.RequireNode24Flag) ?? false;
|
||||||
|
|
||||||
|
var (nodeVersion, configWarningMessage) = NodeUtil.DetermineActionsNodeVersion(environment, useNode24ByDefault, requireNode24);
|
||||||
|
var (finalNodeVersion, platformWarningMessage) = NodeUtil.CheckNodeVersionForLinuxArm32(nodeVersion);
|
||||||
|
nodeData.NodeVersion = finalNodeVersion;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(configWarningMessage))
|
||||||
|
{
|
||||||
|
executionContext.Warning(configWarningMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(platformWarningMessage))
|
||||||
|
{
|
||||||
|
executionContext.Warning(platformWarningMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show information about Node 24 migration in Phase 2
|
||||||
|
if (useNode24ByDefault && !requireNode24 && string.Equals(finalNodeVersion, Constants.Runner.NodeMigration.Node24, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
string infoMessage = "Node 20 is being deprecated. This workflow is running with Node 24 by default. " +
|
||||||
|
"If you need to temporarily use Node 20, you can set the ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true environment variable.";
|
||||||
|
executionContext.Output(infoMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(handler as INodeScriptActionHandler).Data = nodeData;
|
(handler as INodeScriptActionHandler).Data = nodeData;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -9,7 +8,6 @@ using GitHub.Runner.Common;
|
|||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using GitHub.Runner.Worker.Container.ContainerHooks;
|
using GitHub.Runner.Worker.Container.ContainerHooks;
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
|
|
||||||
namespace GitHub.Runner.Worker.Handlers
|
namespace GitHub.Runner.Worker.Handlers
|
||||||
@@ -60,7 +58,14 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
|
|
||||||
public Task<string> DetermineNodeRuntimeVersion(IExecutionContext executionContext, string preferredVersion)
|
public Task<string> DetermineNodeRuntimeVersion(IExecutionContext executionContext, string preferredVersion)
|
||||||
{
|
{
|
||||||
return Task.FromResult<string>(preferredVersion);
|
// Use NodeUtil to check if Node24 is requested but we're on ARM32 Linux
|
||||||
|
var (nodeVersion, warningMessage) = Common.Util.NodeUtil.CheckNodeVersionForLinuxArm32(preferredVersion);
|
||||||
|
if (!string.IsNullOrEmpty(warningMessage))
|
||||||
|
{
|
||||||
|
executionContext.Warning(warningMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(nodeVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int> ExecuteAsync(IExecutionContext context,
|
public async Task<int> ExecuteAsync(IExecutionContext context,
|
||||||
@@ -137,8 +142,12 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
|
|
||||||
public async Task<string> DetermineNodeRuntimeVersion(IExecutionContext executionContext, string preferredVersion)
|
public async Task<string> DetermineNodeRuntimeVersion(IExecutionContext executionContext, string preferredVersion)
|
||||||
{
|
{
|
||||||
// Optimistically use the default
|
// Use NodeUtil to check if Node24 is requested but we're on ARM32 Linux
|
||||||
string nodeExternal = preferredVersion;
|
var (nodeExternal, warningMessage) = Common.Util.NodeUtil.CheckNodeVersionForLinuxArm32(preferredVersion);
|
||||||
|
if (!string.IsNullOrEmpty(warningMessage))
|
||||||
|
{
|
||||||
|
executionContext.Warning(warningMessage);
|
||||||
|
}
|
||||||
|
|
||||||
if (FeatureManager.IsContainerHooksEnabled(executionContext.Global.Variables))
|
if (FeatureManager.IsContainerHooksEnabled(executionContext.Global.Variables))
|
||||||
{
|
{
|
||||||
@@ -264,7 +273,14 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
|
|
||||||
private string CheckPlatformForAlpineContainer(IExecutionContext executionContext, string preferredVersion)
|
private string CheckPlatformForAlpineContainer(IExecutionContext executionContext, string preferredVersion)
|
||||||
{
|
{
|
||||||
string nodeExternal = preferredVersion;
|
// Use NodeUtil to check if Node24 is requested but we're on ARM32 Linux
|
||||||
|
var (nodeExternal, warningMessage) = Common.Util.NodeUtil.CheckNodeVersionForLinuxArm32(preferredVersion);
|
||||||
|
if (!string.IsNullOrEmpty(warningMessage))
|
||||||
|
{
|
||||||
|
executionContext.Warning(warningMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Alpine container compatibility
|
||||||
if (!Constants.Runner.PlatformArchitecture.Equals(Constants.Architecture.X64))
|
if (!Constants.Runner.PlatformArchitecture.Equals(Constants.Architecture.X64))
|
||||||
{
|
{
|
||||||
var os = Constants.Runner.Platform.ToString();
|
var os = Constants.Runner.Platform.ToString();
|
||||||
|
|||||||
@@ -50,8 +50,11 @@ namespace GitHub.Runner.Worker
|
|||||||
if (message.Variables.TryGetValue(Constants.Variables.System.OrchestrationId, out VariableValue orchestrationId) &&
|
if (message.Variables.TryGetValue(Constants.Variables.System.OrchestrationId, out VariableValue orchestrationId) &&
|
||||||
!string.IsNullOrEmpty(orchestrationId.Value))
|
!string.IsNullOrEmpty(orchestrationId.Value))
|
||||||
{
|
{
|
||||||
// make the orchestration id the first item in the user-agent header to avoid get truncated in server log.
|
if (!HostContext.UserAgents.Any(x => string.Equals(x.Product?.Name, "OrchestrationId", StringComparison.OrdinalIgnoreCase)))
|
||||||
HostContext.UserAgents.Insert(0, new ProductInfoHeaderValue("OrchestrationId", orchestrationId.Value));
|
{
|
||||||
|
// make the orchestration id the first item in the user-agent header to avoid get truncated in server log.
|
||||||
|
HostContext.UserAgents.Insert(0, new ProductInfoHeaderValue("OrchestrationId", orchestrationId.Value));
|
||||||
|
}
|
||||||
|
|
||||||
// make sure orchestration id is in the user-agent header.
|
// make sure orchestration id is in the user-agent header.
|
||||||
VssUtil.InitializeVssClientSettings(HostContext.UserAgents, HostContext.WebProxy);
|
VssUtil.InitializeVssClientSettings(HostContext.UserAgents, HostContext.WebProxy);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.24.0" />
|
<PackageReference Include="Azure.Storage.Blobs" Version="12.25.0" />
|
||||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
|
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
{
|
{
|
||||||
queryParams.Add("status", status.Value.ToString());
|
queryParams.Add("status", status.Value.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (runnerVersion != null)
|
if (runnerVersion != null)
|
||||||
{
|
{
|
||||||
queryParams.Add("runnerVersion", runnerVersion);
|
queryParams.Add("runnerVersion", runnerVersion);
|
||||||
@@ -142,7 +143,6 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<TaskAgentSession> CreateSessionAsync(
|
public async Task<TaskAgentSession> CreateSessionAsync(
|
||||||
|
|
||||||
TaskAgentSession session,
|
TaskAgentSession session,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
@@ -191,6 +191,76 @@ namespace GitHub.Actions.RunService.WebApi
|
|||||||
throw new Exception($"Failed to delete broker session: {result.Error}");
|
throw new Exception($"Failed to delete broker session: {result.Error}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task AcknowledgeRunnerRequestAsync(
|
||||||
|
string runnerRequestId,
|
||||||
|
Guid? sessionId,
|
||||||
|
string runnerVersion,
|
||||||
|
TaskAgentStatus? status,
|
||||||
|
string os = null,
|
||||||
|
string architecture = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
// URL
|
||||||
|
var requestUri = new Uri(Client.BaseAddress, "acknowledge");
|
||||||
|
|
||||||
|
// Query parameters
|
||||||
|
List<KeyValuePair<string, string>> queryParams = new List<KeyValuePair<string, string>>();
|
||||||
|
if (sessionId != null)
|
||||||
|
{
|
||||||
|
queryParams.Add("sessionId", sessionId.Value.ToString());
|
||||||
|
}
|
||||||
|
if (status != null)
|
||||||
|
{
|
||||||
|
queryParams.Add("status", status.Value.ToString());
|
||||||
|
}
|
||||||
|
if (runnerVersion != null)
|
||||||
|
{
|
||||||
|
queryParams.Add("runnerVersion", runnerVersion);
|
||||||
|
}
|
||||||
|
if (os != null)
|
||||||
|
{
|
||||||
|
queryParams.Add("os", os);
|
||||||
|
}
|
||||||
|
if (architecture != null)
|
||||||
|
{
|
||||||
|
queryParams.Add("architecture", architecture);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body
|
||||||
|
var payload = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["runnerRequestId"] = runnerRequestId,
|
||||||
|
};
|
||||||
|
var requestContent = new ObjectContent<Dictionary<string, string>>(payload, new VssJsonMediaTypeFormatter(true));
|
||||||
|
|
||||||
|
// POST
|
||||||
|
var result = await SendAsync<object>(
|
||||||
|
new HttpMethod("POST"),
|
||||||
|
requestUri: requestUri,
|
||||||
|
queryParameters: queryParams,
|
||||||
|
content: requestContent,
|
||||||
|
readErrorBody: true,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsSuccess)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryParseErrorBody(result.ErrorBody, out BrokerError brokerError))
|
||||||
|
{
|
||||||
|
switch (brokerError.ErrorKind)
|
||||||
|
{
|
||||||
|
case BrokerErrorKind.RunnerNotFound:
|
||||||
|
throw new RunnerNotFoundException(brokerError.Message);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception($"Failed to acknowledge runner request. Request to {requestUri} failed with status: {result.StatusCode}. Error message {result.Error}");
|
||||||
|
}
|
||||||
|
|
||||||
private static bool TryParseErrorBody(string errorBody, out BrokerError error)
|
private static bool TryParseErrorBody(string errorBody, out BrokerError error)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(errorBody))
|
if (!string.IsNullOrEmpty(errorBody))
|
||||||
|
|||||||
@@ -978,7 +978,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_messageListener.Verify(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()), Times.AtLeast(2));
|
_messageListener.Verify(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()), Times.AtLeast(2));
|
||||||
_messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.AtLeast(2));
|
_messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.AtLeast(2));
|
||||||
_messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once());
|
_messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once());
|
||||||
_credentialManager.Verify(x => x.LoadCredentials(true), Times.Exactly(2));
|
_credentialManager.Verify(x => x.LoadCredentials(true), Times.AtLeast(2));
|
||||||
|
|
||||||
Assert.False(hc.AllowAuthMigration);
|
Assert.False(hc.AllowAuthMigration);
|
||||||
}
|
}
|
||||||
|
|||||||
120
src/Test/L0/Util/NodeUtilL0.cs
Normal file
120
src/Test/L0/Util/NodeUtilL0.cs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Common.Util;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Common.Tests.Util
|
||||||
|
{
|
||||||
|
public class NodeUtilL0
|
||||||
|
{
|
||||||
|
// We're testing the logic with feature flags
|
||||||
|
[Theory]
|
||||||
|
[InlineData(false, false, false, false, "node20", false)] // Phase 1: No env vars
|
||||||
|
[InlineData(false, false, false, true, "node20", false)] // Phase 1: Allow unsecure (redundant)
|
||||||
|
[InlineData(false, false, true, false, "node24", false)] // Phase 1: Force node24
|
||||||
|
[InlineData(false, false, true, true, "node20", true)] // Phase 1: Both flags (use phase default + warning)
|
||||||
|
[InlineData(false, true, false, false, "node24", false)] // Phase 2: No env vars
|
||||||
|
[InlineData(false, true, false, true, "node20", false)] // Phase 2: Allow unsecure
|
||||||
|
[InlineData(false, true, true, false, "node24", false)] // Phase 2: Force node24 (redundant)
|
||||||
|
[InlineData(false, true, true, true, "node24", true)] // Phase 2: Both flags (use phase default + warning)
|
||||||
|
[InlineData(true, false, false, false, "node24", false)] // Phase 3: Always Node 24 regardless of env vars
|
||||||
|
[InlineData(true, false, false, true, "node24", false)] // Phase 3: Always Node 24 regardless of env vars
|
||||||
|
[InlineData(true, false, true, false, "node24", false)] // Phase 3: Always Node 24 regardless of env vars
|
||||||
|
[InlineData(true, false, true, true, "node24", false)] // Phase 3: Always Node 24 regardless of env vars, no warnings in Phase 3
|
||||||
|
public void TestNodeVersionLogic(bool requireNode24, bool useNode24ByDefault, bool forceNode24, bool allowUnsecureNode, string expectedVersion, bool expectWarning)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Environment.SetEnvironmentVariable(Constants.Runner.NodeMigration.ForceNode24Variable, forceNode24 ? "true" : null);
|
||||||
|
Environment.SetEnvironmentVariable(Constants.Runner.NodeMigration.AllowUnsecureNodeVersionVariable, allowUnsecureNode ? "true" : null);
|
||||||
|
|
||||||
|
// Call the actual method
|
||||||
|
var (actualVersion, warningMessage) = NodeUtil.DetermineActionsNodeVersion(null, useNode24ByDefault, requireNode24);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedVersion, actualVersion);
|
||||||
|
|
||||||
|
if (expectWarning)
|
||||||
|
{
|
||||||
|
Assert.NotNull(warningMessage);
|
||||||
|
Assert.Contains("Both", warningMessage);
|
||||||
|
Assert.Contains("are set to true", warningMessage);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert.Null(warningMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Cleanup
|
||||||
|
Environment.SetEnvironmentVariable(Constants.Runner.NodeMigration.ForceNode24Variable, null);
|
||||||
|
Environment.SetEnvironmentVariable(Constants.Runner.NodeMigration.AllowUnsecureNodeVersionVariable, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(false, false, false, false, false, true, "node20", false)] // Phase 1: System env: none, Workflow env: allow=true
|
||||||
|
[InlineData(false, false, true, false, false, false, "node24", false)] // Phase 1: System env: force node24, Workflow env: none
|
||||||
|
[InlineData(false, true, false, false, true, false, "node24", false)] // Phase 1: System env: none, Workflow env: force node24
|
||||||
|
[InlineData(false, false, false, true, false, true, "node20", false)] // Phase 1: System env: allow=true, Workflow env: allow=true (workflow takes precedence)
|
||||||
|
[InlineData(false, false, true, true, false, false, "node20", true)] // Phase 1: System env: both true, Workflow env: none (use phase default + warning)
|
||||||
|
[InlineData(false, false, false, false, true, true, "node20", true)] // Phase 1: System env: none, Workflow env: both (use phase default + warning)
|
||||||
|
[InlineData(true, false, false, false, false, false, "node24", false)] // Phase 2: System env: none, Workflow env: none
|
||||||
|
[InlineData(true, false, false, true, false, false, "node20", false)] // Phase 2: System env: allow=true, Workflow env: none
|
||||||
|
[InlineData(true, false, false, false, false, true, "node20", false)] // Phase 2: System env: none, Workflow env: allow unsecure
|
||||||
|
[InlineData(true, false, true, false, false, true, "node20", false)] // Phase 2: System env: force node24, Workflow env: allow unsecure
|
||||||
|
[InlineData(true, false, true, true, false, false, "node24", true)] // Phase 2: System env: both true, Workflow env: none (use phase default + warning)
|
||||||
|
[InlineData(true, false, false, false, true, true, "node24", true)] // Phase 2: System env: none, Workflow env: both (phase default + warning)
|
||||||
|
[InlineData(false, true, false, false, false, true, "node24", false)] // Phase 3: System env: none, Workflow env: allow=true (always Node 24 in Phase 3)
|
||||||
|
[InlineData(false, true, true, true, false, false, "node24", false)] // Phase 3: System env: both true, Workflow env: none (always Node 24 in Phase 3, no warning)
|
||||||
|
[InlineData(false, true, false, false, true, true, "node24", false)] // Phase 3: System env: none, Workflow env: both (always Node 24 in Phase 3, no warning)
|
||||||
|
public void TestNodeVersionLogicWithWorkflowEnvironment(bool useNode24ByDefault, bool requireNode24,
|
||||||
|
bool systemForceNode24, bool systemAllowUnsecure,
|
||||||
|
bool workflowForceNode24, bool workflowAllowUnsecure,
|
||||||
|
string expectedVersion, bool expectWarning)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Set system environment variables
|
||||||
|
Environment.SetEnvironmentVariable(Constants.Runner.NodeMigration.ForceNode24Variable, systemForceNode24 ? "true" : null);
|
||||||
|
Environment.SetEnvironmentVariable(Constants.Runner.NodeMigration.AllowUnsecureNodeVersionVariable, systemAllowUnsecure ? "true" : null);
|
||||||
|
|
||||||
|
// Set workflow environment variables
|
||||||
|
var workflowEnv = new Dictionary<string, string>();
|
||||||
|
if (workflowForceNode24)
|
||||||
|
{
|
||||||
|
workflowEnv[Constants.Runner.NodeMigration.ForceNode24Variable] = "true";
|
||||||
|
}
|
||||||
|
if (workflowAllowUnsecure)
|
||||||
|
{
|
||||||
|
workflowEnv[Constants.Runner.NodeMigration.AllowUnsecureNodeVersionVariable] = "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the actual method with our test parameters
|
||||||
|
var (actualVersion, warningMessage) = NodeUtil.DetermineActionsNodeVersion(workflowEnv, useNode24ByDefault, requireNode24);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedVersion, actualVersion);
|
||||||
|
|
||||||
|
if (expectWarning)
|
||||||
|
{
|
||||||
|
Assert.NotNull(warningMessage);
|
||||||
|
Assert.Contains("Both", warningMessage);
|
||||||
|
Assert.Contains("are set to true", warningMessage);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert.Null(warningMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Cleanup
|
||||||
|
Environment.SetEnvironmentVariable(Constants.Runner.NodeMigration.ForceNode24Variable, null);
|
||||||
|
Environment.SetEnvironmentVariable(Constants.Runner.NodeMigration.AllowUnsecureNodeVersionVariable, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1659,6 +1659,76 @@ runs:
|
|||||||
Teardown();
|
Teardown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void LoadsNode24ActionDefinition()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange.
|
||||||
|
Setup();
|
||||||
|
const string Content = @"
|
||||||
|
# Container action
|
||||||
|
name: 'Hello World'
|
||||||
|
description: 'Greet the world and record the time'
|
||||||
|
author: 'GitHub'
|
||||||
|
inputs:
|
||||||
|
greeting: # id of input
|
||||||
|
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
||||||
|
required: true
|
||||||
|
default: 'Hello'
|
||||||
|
entryPoint: # id of input
|
||||||
|
description: 'optional docker entrypoint overwrite.'
|
||||||
|
required: false
|
||||||
|
outputs:
|
||||||
|
time: # id of output
|
||||||
|
description: 'The time we did the greeting'
|
||||||
|
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
|
||||||
|
color: 'green' # optional, decorates the entry in the GitHub Marketplace
|
||||||
|
runs:
|
||||||
|
using: 'node24'
|
||||||
|
main: 'task.js'
|
||||||
|
";
|
||||||
|
Pipelines.ActionStep instance;
|
||||||
|
string directory;
|
||||||
|
CreateAction(yamlContent: Content, instance: out instance, directory: out directory);
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
Definition definition = _actionManager.LoadAction(_ec.Object, instance);
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.NotNull(definition);
|
||||||
|
Assert.Equal(directory, definition.Directory);
|
||||||
|
Assert.NotNull(definition.Data);
|
||||||
|
Assert.NotNull(definition.Data.Inputs); // inputs
|
||||||
|
Dictionary<string, string> inputDefaults = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var input in definition.Data.Inputs)
|
||||||
|
{
|
||||||
|
var name = input.Key.AssertString("key").Value;
|
||||||
|
var value = input.Value.AssertScalar("value").ToString();
|
||||||
|
|
||||||
|
_hc.GetTrace().Info($"Default: {name} = {value}");
|
||||||
|
inputDefaults[name] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Equal(2, inputDefaults.Count);
|
||||||
|
Assert.True(inputDefaults.ContainsKey("greeting"));
|
||||||
|
Assert.Equal("Hello", inputDefaults["greeting"]);
|
||||||
|
Assert.True(string.IsNullOrEmpty(inputDefaults["entryPoint"]));
|
||||||
|
Assert.NotNull(definition.Data.Execution); // execution
|
||||||
|
|
||||||
|
Assert.NotNull(definition.Data.Execution as NodeJSActionExecutionData);
|
||||||
|
Assert.Equal("task.js", (definition.Data.Execution as NodeJSActionExecutionData).Script);
|
||||||
|
Assert.Equal("node24", (definition.Data.Execution as NodeJSActionExecutionData).NodeVersion);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
|
|||||||
@@ -502,6 +502,49 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void Load_Node24Action()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
Setup();
|
||||||
|
|
||||||
|
var actionManifest = new ActionManifestManager();
|
||||||
|
actionManifest.Initialize(_hc);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "node24action.yml"));
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
Assert.Equal("Hello World", result.Name);
|
||||||
|
Assert.Equal("Greet the world and record the time", result.Description);
|
||||||
|
Assert.Equal(2, result.Inputs.Count);
|
||||||
|
Assert.Equal("greeting", result.Inputs[0].Key.AssertString("key").Value);
|
||||||
|
Assert.Equal("Hello", result.Inputs[0].Value.AssertString("value").Value);
|
||||||
|
Assert.Equal("entryPoint", result.Inputs[1].Key.AssertString("key").Value);
|
||||||
|
Assert.Equal("", result.Inputs[1].Value.AssertString("value").Value);
|
||||||
|
Assert.Equal(1, result.Deprecated.Count);
|
||||||
|
|
||||||
|
Assert.True(result.Deprecated.ContainsKey("greeting"));
|
||||||
|
result.Deprecated.TryGetValue("greeting", out string value);
|
||||||
|
Assert.Equal("This property has been deprecated", value);
|
||||||
|
|
||||||
|
Assert.Equal(ActionExecutionType.NodeJS, result.Execution.ExecutionType);
|
||||||
|
|
||||||
|
var nodeAction = result.Execution as NodeJSActionExecutionData;
|
||||||
|
|
||||||
|
Assert.Equal("main.js", nodeAction.Script);
|
||||||
|
Assert.Equal("node24", nodeAction.NodeVersion);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
@@ -758,7 +801,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
//Assert
|
//Assert
|
||||||
var err = Assert.Throws<ArgumentException>(() => actionManifest.Load(_ec.Object, action_path));
|
var err = Assert.Throws<ArgumentException>(() => actionManifest.Load(_ec.Object, action_path));
|
||||||
Assert.Contains($"Failed to load {action_path}", err.Message);
|
Assert.Contains($"Failed to load {action_path}", err.Message);
|
||||||
_ec.Verify(x => x.AddIssue(It.Is<Issue>(s => s.Message.Contains("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12', 'node16' or 'node20'.")), It.IsAny<ExecutionContextLogOptions>()), Times.Once);
|
_ec.Verify(x => x.AddIssue(It.Is<Issue>(s => s.Message.Contains("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12', 'node16', 'node20' or 'node24'.")), It.IsAny<ExecutionContextLogOptions>()), Times.Once);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
[InlineData("node12", "node20")]
|
[InlineData("node12", "node20")]
|
||||||
[InlineData("node16", "node20")]
|
[InlineData("node16", "node20")]
|
||||||
[InlineData("node20", "node20")]
|
[InlineData("node20", "node20")]
|
||||||
|
[InlineData("node24", "node24")]
|
||||||
public void IsNodeVersionUpgraded(string inputVersion, string expectedVersion)
|
public void IsNodeVersionUpgraded(string inputVersion, string expectedVersion)
|
||||||
{
|
{
|
||||||
using (TestHostContext hc = CreateTestContext())
|
using (TestHostContext hc = CreateTestContext())
|
||||||
@@ -41,7 +42,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
var hf = new HandlerFactory();
|
var hf = new HandlerFactory();
|
||||||
hf.Initialize(hc);
|
hf.Initialize(hc);
|
||||||
|
|
||||||
// Server Feature Flag
|
// Setup variables
|
||||||
var variables = new Dictionary<string, VariableValue>();
|
var variables = new Dictionary<string, VariableValue>();
|
||||||
Variables serverVariables = new(hc, variables);
|
Variables serverVariables = new(hc, variables);
|
||||||
|
|
||||||
@@ -72,5 +73,48 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
Assert.Equal(expectedVersion, handler.Data.NodeVersion);
|
Assert.Equal(expectedVersion, handler.Data.NodeVersion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void Node24ExplicitlyRequested_HonoredByDefault()
|
||||||
|
{
|
||||||
|
using (TestHostContext hc = CreateTestContext())
|
||||||
|
{
|
||||||
|
// Arrange.
|
||||||
|
var hf = new HandlerFactory();
|
||||||
|
hf.Initialize(hc);
|
||||||
|
|
||||||
|
// Basic variables setup
|
||||||
|
var variables = new Dictionary<string, VariableValue>();
|
||||||
|
Variables serverVariables = new(hc, variables);
|
||||||
|
|
||||||
|
_ec.Setup(x => x.Global).Returns(new GlobalContext()
|
||||||
|
{
|
||||||
|
Variables = serverVariables,
|
||||||
|
EnvironmentVariables = new Dictionary<string, string>()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act - Node 24 explicitly requested in action.yml
|
||||||
|
var data = new NodeJSActionExecutionData();
|
||||||
|
data.NodeVersion = "node24";
|
||||||
|
var handler = hf.Create(
|
||||||
|
_ec.Object,
|
||||||
|
new ScriptReference(),
|
||||||
|
new Mock<IStepHost>().Object,
|
||||||
|
data,
|
||||||
|
new Dictionary<string, string>(),
|
||||||
|
new Dictionary<string, string>(),
|
||||||
|
new Variables(hc, new Dictionary<string, VariableValue>()),
|
||||||
|
"",
|
||||||
|
new List<JobExtensionRunner>()
|
||||||
|
) as INodeScriptActionHandler;
|
||||||
|
|
||||||
|
// Assert - should be node24 as requested
|
||||||
|
Assert.Equal("node24", handler.Data.NodeVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
35
src/Test/L0/Worker/Handlers/NodeHandlerL0.cs
Normal file
35
src/Test/L0/Worker/Handlers/NodeHandlerL0.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
using GitHub.Runner.Worker;
|
||||||
|
using GitHub.Runner.Worker.Handlers;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Common.Tests.Worker.Handlers
|
||||||
|
{
|
||||||
|
public sealed class NodeHandlerL0
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void NodeJSActionExecutionDataSupportsNode24()
|
||||||
|
{
|
||||||
|
// Create NodeJSActionExecutionData with node24
|
||||||
|
var nodeJSData = new NodeJSActionExecutionData
|
||||||
|
{
|
||||||
|
NodeVersion = "node24",
|
||||||
|
Script = "test.js"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.Equal("node24", nodeJSData.NodeVersion);
|
||||||
|
Assert.Equal(ActionExecutionType.NodeJS, nodeJSData.ExecutionType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -162,6 +162,60 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
Assert.Equal("node20", nodeVersion);
|
Assert.Equal("node20", nodeVersion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public async Task DetermineNode24RuntimeVersionInAlpineContainerAsync()
|
||||||
|
{
|
||||||
|
using (TestHostContext hc = CreateTestContext())
|
||||||
|
{
|
||||||
|
// Arrange.
|
||||||
|
var sh = new ContainerStepHost();
|
||||||
|
sh.Initialize(hc);
|
||||||
|
sh.Container = new ContainerInfo() { ContainerId = "1234abcd" };
|
||||||
|
|
||||||
|
_dc.Setup(d => d.DockerExec(_ec.Object, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<List<string>>()))
|
||||||
|
.Callback((IExecutionContext ec, string id, string options, string command, List<string> output) =>
|
||||||
|
{
|
||||||
|
output.Add("alpine");
|
||||||
|
})
|
||||||
|
.ReturnsAsync(0);
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
var nodeVersion = await sh.DetermineNodeRuntimeVersion(_ec.Object, "node24");
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.Equal("node24_alpine", nodeVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public async Task DetermineNode24RuntimeVersionInUnknownContainerAsync()
|
||||||
|
{
|
||||||
|
using (TestHostContext hc = CreateTestContext())
|
||||||
|
{
|
||||||
|
// Arrange.
|
||||||
|
var sh = new ContainerStepHost();
|
||||||
|
sh.Initialize(hc);
|
||||||
|
sh.Container = new ContainerInfo() { ContainerId = "1234abcd" };
|
||||||
|
|
||||||
|
_dc.Setup(d => d.DockerExec(_ec.Object, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<List<string>>()))
|
||||||
|
.Callback((IExecutionContext ec, string id, string options, string command, List<string> output) =>
|
||||||
|
{
|
||||||
|
output.Add("github");
|
||||||
|
})
|
||||||
|
.ReturnsAsync(0);
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
var nodeVersion = await sh.DetermineNodeRuntimeVersion(_ec.Object, "node24");
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.Equal("node24", nodeVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
63
src/Test/L0/Worker/StepHostNodeVersionL0.cs
Normal file
63
src/Test/L0/Worker/StepHostNodeVersionL0.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
using GitHub.Runner.Worker;
|
||||||
|
using GitHub.Runner.Worker.Handlers;
|
||||||
|
using Moq;
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Common.Tests.Worker
|
||||||
|
{
|
||||||
|
public sealed class StepHostNodeVersionL0
|
||||||
|
{
|
||||||
|
private Mock<IExecutionContext> _ec;
|
||||||
|
private DefaultStepHost _defaultStepHost;
|
||||||
|
|
||||||
|
public StepHostNodeVersionL0()
|
||||||
|
{
|
||||||
|
_ec = new Mock<IExecutionContext>();
|
||||||
|
_defaultStepHost = new DefaultStepHost();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void CheckNodeVersionForArm32_Node24OnArm32Linux()
|
||||||
|
{
|
||||||
|
// Test via NodeUtil directly
|
||||||
|
string preferredVersion = "node24";
|
||||||
|
var (nodeVersion, warningMessage) = Common.Util.NodeUtil.CheckNodeVersionForLinuxArm32(preferredVersion);
|
||||||
|
|
||||||
|
// On ARM32 Linux, we should fall back to node20
|
||||||
|
bool isArm32 = RuntimeInformation.ProcessArchitecture == Architecture.Arm ||
|
||||||
|
Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")?.Contains("ARM") == true;
|
||||||
|
bool isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||||
|
|
||||||
|
if (isArm32 && isLinux)
|
||||||
|
{
|
||||||
|
// Should downgrade to node20 on ARM32 Linux
|
||||||
|
Assert.Equal("node20", nodeVersion);
|
||||||
|
Assert.NotNull(warningMessage);
|
||||||
|
Assert.Contains("Node 24 is not supported on Linux ARM32 platforms", warningMessage);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// On non-ARM32 platforms, should pass through the version unmodified
|
||||||
|
Assert.Equal("node24", nodeVersion);
|
||||||
|
Assert.Null(warningMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void CheckNodeVersionForArm32_PassThroughNonNode24Versions()
|
||||||
|
{
|
||||||
|
string preferredVersion = "node20";
|
||||||
|
var (nodeVersion, warningMessage) = Common.Util.NodeUtil.CheckNodeVersionForLinuxArm32(preferredVersion);
|
||||||
|
|
||||||
|
// Should never modify the version for non-node24 inputs
|
||||||
|
Assert.Equal("node20", nodeVersion);
|
||||||
|
Assert.Null(warningMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||||
<PackageReference Include="xunit" Version="2.7.1" />
|
<PackageReference Include="xunit" Version="2.7.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||||
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.7.0" />
|
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.7.0" />
|
||||||
|
|||||||
20
src/Test/TestData/node24action.yml
Normal file
20
src/Test/TestData/node24action.yml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
name: 'Hello World'
|
||||||
|
description: 'Greet the world and record the time'
|
||||||
|
author: 'Test Corporation'
|
||||||
|
inputs:
|
||||||
|
greeting: # id of input
|
||||||
|
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
||||||
|
required: true
|
||||||
|
default: 'Hello'
|
||||||
|
deprecationMessage: 'This property has been deprecated'
|
||||||
|
entryPoint: # id of input
|
||||||
|
description: 'optional docker entrypoint overwrite.'
|
||||||
|
required: false
|
||||||
|
outputs:
|
||||||
|
time: # id of output
|
||||||
|
description: 'The time we did the greeting'
|
||||||
|
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
|
||||||
|
color: 'green' # optional, decorates the entry in the GitHub Marketplace
|
||||||
|
runs:
|
||||||
|
using: 'node24'
|
||||||
|
main: 'main.js'
|
||||||
@@ -17,7 +17,7 @@ LAYOUT_DIR="$SCRIPT_DIR/../_layout"
|
|||||||
DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
|
DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
|
||||||
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
||||||
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
||||||
DOTNETSDK_VERSION="8.0.411"
|
DOTNETSDK_VERSION="8.0.413"
|
||||||
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
||||||
RUNNER_VERSION=$(cat runnerversion)
|
RUNNER_VERSION=$(cat runnerversion)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"sdk": {
|
"sdk": {
|
||||||
"version": "8.0.411"
|
"version": "8.0.413"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.326.0
|
2.328.0
|
||||||
|
|||||||
Reference in New Issue
Block a user