Compare commits

...

23 Commits

Author SHA1 Message Date
Salman Chishti
9513afd82f Runner patch version 2.327.1 (#3959) 2025-07-25 17:15:09 +01:00
Salman Chishti
ff3fafcc38 Fix if statement structure in update script and variable reference (#3956) 2025-07-25 16:00:44 +00:00
Salman Chishti
f54fe81688 Create 2.327.0 runner releases 2025-07-22 19:44:29 +01:00
Salman Chishti
dc9cf684c9 Prepare runner release 2.327.0 (#3951) 2025-07-22 18:59:15 +01:00
github-actions[bot]
c765c990b9 Update dotnet sdk to latest version @8.0.412 (#3941)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-22 11:32:03 -04:00
Salman Chishti
ed48ddd08c Runner Support for executing Node24 Actions (#3940) 2025-07-17 01:00:17 +00:00
Salman Chishti
a1e6ad8d2e Fix null reference exception in user agent handling (#3946) 2025-07-17 01:12:03 +01:00
Tingluo Huang
14856e63bc Try add orchestrationid into user-agent using token claim. (#3945) 2025-07-16 14:11:09 -04:00
Tingluo Huang
0d24afa114 Prepare 2.326.0 runner release. (#3936) 2025-07-07 15:19:21 -04:00
Tingluo Huang
20912234a5 Upgrade node.js to latest version. (#3935) 2025-07-07 11:47:48 -04:00
Tingluo Huang
5969cbe208 Bump windows service app to dotnet 4.7 (#3926) 2025-07-01 15:49:30 -04:00
github-actions[bot]
9f57d37642 Update Docker to v28.2.2 and Buildx to v0.25.0 (#3918)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-30 12:43:59 -04:00
github-actions[bot]
60563d82d1 Update dotnet sdk to latest version @8.0.411 (#3911)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-30 11:42:02 -04:00
Ben De St Paer-Gotch
097ada9374 Update README.md (#3898) 2025-06-09 18:16:35 +01:00
Ryan Ghadimi
9b457781d6 runner timestamps invariant (#3888) 2025-06-02 21:47:51 +00:00
Aiqiao Yan
9709b69571 prep for version 2.325.0 (#3889) 2025-06-02 14:40:01 -04:00
Tingluo Huang
acf3f2ba12 Allow NO_SSL_VERIFY in RawHttpMessageHandler. (#3883) 2025-05-30 22:48:16 -04:00
github-actions[bot]
f03fcc8a01 Update Docker to v28.2.1 and Buildx to v0.24.0 (#3881)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-29 18:43:10 -04:00
github-actions[bot]
e4e103c5ed Update dotnet sdk to latest version @8.0.410 (#3871)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-29 17:56:04 -04:00
Aiqiao Yan
a906ec302b show helpful error message when resolving actions directly with launch (#3874) 2025-05-28 22:46:25 -04:00
Tingluo Huang
d9e714496d Allow runner to use authv2 during config. (#3866) 2025-05-21 16:49:35 -04:00
github-actions[bot]
df189ba6e3 Update dotnet sdk to latest version @8.0.409 (#3860)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-19 01:43:01 +00:00
Tingluo Huang
4c1de69e1c Create schedule workflow to upgrade docker and buildx version. (#3859) 2025-05-14 10:04:01 -04:00
41 changed files with 859 additions and 92 deletions

View File

@@ -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.408" "version": "8.0.412"
}, },
"ghcr.io/devcontainers/features/node:1": { "ghcr.io/devcontainers/features/node:1": {
"version": "20" "version": "20"

View File

@@ -41,7 +41,7 @@ jobs:
devScript: ./dev.sh devScript: ./dev.sh
- runtime: win-x64 - runtime: win-x64
os: windows-2019 os: windows-latest
devScript: ./dev devScript: ./dev
- runtime: win-arm64 - runtime: win-arm64

View File

@@ -0,0 +1,144 @@
name: "Docker/Buildx Version Upgrade"
on:
schedule:
- cron: '0 0 * * 1' # Run every Monday at midnight
workflow_dispatch: # Allow manual triggering
jobs:
check-versions:
runs-on: ubuntu-latest
outputs:
DOCKER_SHOULD_UPDATE: ${{ steps.check_docker_version.outputs.SHOULD_UPDATE }}
DOCKER_LATEST_VERSION: ${{ steps.check_docker_version.outputs.LATEST_VERSION }}
DOCKER_CURRENT_VERSION: ${{ steps.check_docker_version.outputs.CURRENT_VERSION }}
BUILDX_SHOULD_UPDATE: ${{ steps.check_buildx_version.outputs.SHOULD_UPDATE }}
BUILDX_LATEST_VERSION: ${{ steps.check_buildx_version.outputs.LATEST_VERSION }}
BUILDX_CURRENT_VERSION: ${{ steps.check_buildx_version.outputs.CURRENT_VERSION }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Check Docker version
id: check_docker_version
shell: bash
run: |
# Extract current Docker version from Dockerfile
current_version=$(grep "ARG DOCKER_VERSION=" ./images/Dockerfile | cut -d'=' -f2)
# Fetch latest Docker Engine version from Docker's download site
# This gets the latest Linux static binary version which matches what's used in the Dockerfile
latest_version=$(curl -s https://download.docker.com/linux/static/stable/x86_64/ | grep -o 'docker-[0-9]*\.[0-9]*\.[0-9]*\.tgz' | sort -V | tail -n 1 | sed 's/docker-\(.*\)\.tgz/\1/')
# Extra check to ensure we got a valid version
if [[ ! $latest_version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Failed to retrieve a valid Docker version"
exit 1
fi
should_update=0
[ "$current_version" != "$latest_version" ] && should_update=1
echo "CURRENT_VERSION=${current_version}" >> $GITHUB_OUTPUT
echo "LATEST_VERSION=${latest_version}" >> $GITHUB_OUTPUT
echo "SHOULD_UPDATE=${should_update}" >> $GITHUB_OUTPUT
- name: Check Buildx version
id: check_buildx_version
shell: bash
run: |
# Extract current Buildx version from Dockerfile
current_version=$(grep "ARG BUILDX_VERSION=" ./images/Dockerfile | cut -d'=' -f2)
# Fetch latest Buildx version
latest_version=$(curl -s https://api.github.com/repos/docker/buildx/releases/latest | jq -r '.tag_name' | sed 's/^v//')
should_update=0
[ "$current_version" != "$latest_version" ] && should_update=1
echo "CURRENT_VERSION=${current_version}" >> $GITHUB_OUTPUT
echo "LATEST_VERSION=${latest_version}" >> $GITHUB_OUTPUT
echo "SHOULD_UPDATE=${should_update}" >> $GITHUB_OUTPUT
- name: Create annotations for versions
run: |
docker_should_update="${{ steps.check_docker_version.outputs.SHOULD_UPDATE }}"
buildx_should_update="${{ steps.check_buildx_version.outputs.SHOULD_UPDATE }}"
# Show annotation if only Docker needs update
if [[ "$docker_should_update" == "1" && "$buildx_should_update" == "0" ]]; then
echo "::warning ::Docker version (${{ steps.check_docker_version.outputs.LATEST_VERSION }}) needs update but Buildx is current. Only updating when both need updates."
fi
# Show annotation if only Buildx needs update
if [[ "$docker_should_update" == "0" && "$buildx_should_update" == "1" ]]; then
echo "::warning ::Buildx version (${{ steps.check_buildx_version.outputs.LATEST_VERSION }}) needs update but Docker is current. Only updating when both need updates."
fi
# Show annotation when both are current
if [[ "$docker_should_update" == "0" && "$buildx_should_update" == "0" ]]; then
echo "::warning ::Latest Docker version is ${{ steps.check_docker_version.outputs.LATEST_VERSION }} and Buildx version is ${{ steps.check_buildx_version.outputs.LATEST_VERSION }}. No updates needed."
fi
update-versions:
permissions:
pull-requests: write
contents: write
needs: [check-versions]
if: ${{ needs.check-versions.outputs.DOCKER_SHOULD_UPDATE == 1 && needs.check-versions.outputs.BUILDX_SHOULD_UPDATE == 1 }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Update Docker version
shell: bash
run: |
latest_version="${{ needs.check-versions.outputs.DOCKER_LATEST_VERSION }}"
current_version="${{ needs.check-versions.outputs.DOCKER_CURRENT_VERSION }}"
# Update version in Dockerfile
sed -i "s/ARG DOCKER_VERSION=$current_version/ARG DOCKER_VERSION=$latest_version/g" ./images/Dockerfile
- name: Update Buildx version
shell: bash
run: |
latest_version="${{ needs.check-versions.outputs.BUILDX_LATEST_VERSION }}"
current_version="${{ needs.check-versions.outputs.BUILDX_CURRENT_VERSION }}"
# Update version in Dockerfile
sed -i "s/ARG BUILDX_VERSION=$current_version/ARG BUILDX_VERSION=$latest_version/g" ./images/Dockerfile
- name: Commit changes and create Pull Request
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Setup branch and commit information
branch_name="feature/docker-buildx-upgrade"
commit_message="Upgrade Docker to v${{ needs.check-versions.outputs.DOCKER_LATEST_VERSION }} and Buildx to v${{ needs.check-versions.outputs.BUILDX_LATEST_VERSION }}"
pr_title="Update Docker to v${{ needs.check-versions.outputs.DOCKER_LATEST_VERSION }} and Buildx to v${{ needs.check-versions.outputs.BUILDX_LATEST_VERSION }}"
# Configure git
git config --global user.name "github-actions[bot]"
git config --global user.email "<41898282+github-actions[bot]@users.noreply.github.com>"
# Create branch or switch to it if it exists
if git show-ref --quiet refs/remotes/origin/$branch_name; then
git fetch origin
git checkout -B "$branch_name" origin/$branch_name
else
git checkout -b "$branch_name"
fi
# Commit and push changes
git commit -a -m "$commit_message"
git push --force origin "$branch_name"
# Create PR
pr_body="Upgrades Docker version from ${{ needs.check-versions.outputs.DOCKER_CURRENT_VERSION }} to ${{ needs.check-versions.outputs.DOCKER_LATEST_VERSION }} and Docker Buildx version from ${{ needs.check-versions.outputs.BUILDX_CURRENT_VERSION }} to ${{ needs.check-versions.outputs.BUILDX_LATEST_VERSION }}.\n\n"
pr_body+="Release notes: https://docs.docker.com/engine/release-notes/\n\n"
pr_body+="---\n\nAutogenerated by [Docker/Buildx Version Upgrade Workflow](https://github.com/actions/runner/blob/main/.github/workflows/docker-buildx-upgrade.yml)"
gh pr create -B main -H "$branch_name" \
--title "$pr_title" \
--body "$pr_body"

View File

@@ -77,7 +77,7 @@ jobs:
devScript: ./dev.sh devScript: ./dev.sh
- runtime: win-x64 - runtime: win-x64
os: windows-2019 os: windows-latest
devScript: ./dev devScript: ./dev
- runtime: win-arm64 - runtime: win-arm64

View File

@@ -20,6 +20,20 @@ Runner releases:
![linux](docs/res/linux_sm.png) [Pre-reqs](docs/start/envlinux.md) | [Download](https://github.com/actions/runner/releases) ![linux](docs/res/linux_sm.png) [Pre-reqs](docs/start/envlinux.md) | [Download](https://github.com/actions/runner/releases)
## Contribute ### Note
We accept contributions in the form of issues and pull requests. The runner typically requires changes across the entire system and we aim for issues in the runner to be entirely self contained and fixable here. Therefore, we will primarily handle bug issues opened in this repo and we kindly request you to create all feature and enhancement requests on the [GitHub Feedback](https://github.com/community/community/discussions/categories/actions-and-packages) page. [Read more about our guidelines here](docs/contribute.md) before contributing. Thank you for your interest in this GitHub repo, however, right now we are not taking contributions.
We continue to focus our resources on strategic areas that help our customers be successful while making developers' lives easier. While GitHub Actions remains a key part of this vision, we are allocating resources towards other areas of Actions and are not taking contributions to this repository at this time. The GitHub public roadmap is the best place to follow along for any updates on features were working on and what stage theyre in.
We are taking the following steps to better direct requests related to GitHub Actions, including:
1. We will be directing questions and support requests to our [Community Discussions area](https://github.com/orgs/community/discussions/categories/actions)
2. High Priority bugs can be reported through Community Discussions or you can report these to our support team https://support.github.com/contact/bug-report.
3. Security Issues should be handled as per our [security.md](security.md)
We will still provide security updates for this project and fix major breaking changes during this time.
You are welcome to still raise bugs in this repo.

View File

@@ -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`

View File

@@ -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.1.1 ARG DOCKER_VERSION=28.3.0
ARG BUILDX_VERSION=0.23.0 ARG BUILDX_VERSION=0.25.0
RUN apt update -y && apt install curl unzip -y RUN apt update -y && apt install curl unzip -y

View File

@@ -1,37 +1,13 @@
## What's Changed ## What's Changed
* Increase error body max length before truncation by @ericsciple in https://github.com/actions/runner/pull/3762 * Try add orchestrationid into user-agent using token claim. by @TingluoHuang in https://github.com/actions/runner/pull/3945
* Fix release.yml break by upgrading actions/github-script by @TingluoHuang in https://github.com/actions/runner/pull/3772 * Fix null reference exception in user agent handling by @salmanmkc in https://github.com/actions/runner/pull/3946
* Small runner code cleanup. by @TingluoHuang in https://github.com/actions/runner/pull/3773 * Runner Support for executing Node24 Actions by @salmanmkc in https://github.com/actions/runner/pull/3940
* Enable hostcontext to track auth migration. by @TingluoHuang in https://github.com/actions/runner/pull/3776 * Update dotnet sdk to latest version @8.0.412 by @github-actions[bot] in https://github.com/actions/runner/pull/3941
* Add option in OAuthCred to load authUrlV2. by @TingluoHuang in https://github.com/actions/runner/pull/3777
* Remove create session with broker in MessageListener. by @TingluoHuang in https://github.com/actions/runner/pull/3782
* Enable auth migration based on config refresh. by @TingluoHuang in https://github.com/actions/runner/pull/3786
* Set JWT.alg to PS256 with PssPadding. by @TingluoHuang in https://github.com/actions/runner/pull/3789
* Enable FIPS by default. by @TingluoHuang in https://github.com/actions/runner/pull/3793
* Support auth migration using authUrlV2 in Runner/MessageListener. by @TingluoHuang in https://github.com/actions/runner/pull/3787
* Cleanup feature flag actions_skip_retry_complete_job_upon_known_errors by @ericsciple in https://github.com/actions/runner/pull/3806
* Update dotnet sdk to latest version @8.0.408 by @github-actions in https://github.com/actions/runner/pull/3808
* Bump hook to 0.7.0 by @nikola-jokic in https://github.com/actions/runner/pull/3813
* Allow enable auth migration by default. by @TingluoHuang in https://github.com/actions/runner/pull/3804
* Do not retry /renewjob on 404 by @ericsciple in https://github.com/actions/runner/pull/3828
* Bump Microsoft.NET.Test.Sdk from 17.12.0 to 17.13.0 in /src by @dependabot in https://github.com/actions/runner/pull/3719
* Add copilot-instructions.md by @pje in https://github.com/actions/runner/pull/3810
* Bump actions/upload-release-asset from 1.0.1 to 1.0.2 by @dependabot in https://github.com/actions/runner/pull/3553
* Ignore exception during auth migration. by @TingluoHuang in https://github.com/actions/runner/pull/3835
* feat: default fromPath for problem matchers by @dsanders11 in https://github.com/actions/runner/pull/3802
* Bump Azure.Storage.Blobs from 12.23.0 to 12.24.0 in /src by @dependabot in https://github.com/actions/runner/pull/3837
* Bump nodejs version. by @TingluoHuang in https://github.com/actions/runner/pull/3840
* Feature-flagged support for `JobContext.CheckRunID` by @pje in https://github.com/actions/runner/pull/3811
* Bump System.ServiceProcess.ServiceController from 8.0.0 to 8.0.1 in /src by @dependabot in https://github.com/actions/runner/pull/3844
* Bump xunit.runner.visualstudio from 2.5.8 to 2.8.2 in /src by @dependabot in https://github.com/actions/runner/pull/3845
* Make sure the token's claims are match as expected. by @TingluoHuang in https://github.com/actions/runner/pull/3846
* Prefer _migrated config on startup by @lokesh755 in https://github.com/actions/runner/pull/3853
* Update docker and buildx by @TingluoHuang in https://github.com/actions/runner/pull/3854
## New Contributors ## New Contributors
* @dsanders11 made their first contribution in https://github.com/actions/runner/pull/3802 * @salmanmkc made their first contribution in https://github.com/actions/runner/pull/3946
**Full Changelog**: https://github.com/actions/runner/compare/v2.323.0...v2.324.0 **Full Changelog**: https://github.com/actions/runner/compare/v2.326.0...v2.327.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.

View File

@@ -1 +1 @@
<Update to ./src/runnerversion when creating release> 2.327.1

View File

@@ -716,9 +716,10 @@
} }
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.11", "version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@@ -4751,9 +4752,9 @@
} }
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"

View File

@@ -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.1" NODE20_VERSION="20.19.3"
NODE24_VERSION="24.4.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

View File

@@ -3299,7 +3299,7 @@ function expand(str, isTop) {
var isOptions = m.body.indexOf(',') >= 0; var isOptions = m.body.indexOf(',') >= 0;
if (!isSequence && !isOptions) { if (!isSequence && !isOptions) {
// {a},b} // {a},b}
if (m.post.match(/,.*\}/)) { if (m.post.match(/,(?!,).*\}/)) {
str = m.pre + '{' + m.body + escClose + m.post; str = m.pre + '{' + m.body + escClose + m.post;
return expand(str); return expand(str);
} }

View File

@@ -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" ]]

View File

@@ -168,6 +168,7 @@ namespace GitHub.Runner.Common
public static readonly string UseContainerPathForTemplate = "DistributedTask.UseContainerPathForTemplate"; public static readonly string UseContainerPathForTemplate = "DistributedTask.UseContainerPathForTemplate";
public static readonly string AllowRunnerContainerHooks = "DistributedTask.AllowRunnerContainerHooks"; public static readonly string AllowRunnerContainerHooks = "DistributedTask.AllowRunnerContainerHooks";
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 InternalTelemetryIssueDataKey = "_internal_telemetry"; public static readonly string InternalTelemetryIssueDataKey = "_internal_telemetry";

View File

@@ -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);

View File

@@ -15,7 +15,7 @@ namespace GitHub.Runner.Common
{ {
void InitializeLaunchClient(Uri uri, string token); void InitializeLaunchClient(Uri uri, string token);
Task<ActionDownloadInfoCollection> ResolveActionsDownloadInfoAsync(Guid planId, Guid jobId, ActionReferenceList actionReferenceList, CancellationToken cancellationToken); Task<ActionDownloadInfoCollection> ResolveActionsDownloadInfoAsync(Guid planId, Guid jobId, ActionReferenceList actionReferenceList, CancellationToken cancellationToken, bool displayHelpfulActionsDownloadErrors);
} }
public sealed class LaunchServer : RunnerService, ILaunchServer public sealed class LaunchServer : RunnerService, ILaunchServer
@@ -42,12 +42,16 @@ namespace GitHub.Runner.Common
} }
public Task<ActionDownloadInfoCollection> ResolveActionsDownloadInfoAsync(Guid planId, Guid jobId, ActionReferenceList actionReferenceList, public Task<ActionDownloadInfoCollection> ResolveActionsDownloadInfoAsync(Guid planId, Guid jobId, ActionReferenceList actionReferenceList,
CancellationToken cancellationToken) CancellationToken cancellationToken, bool displayHelpfulActionsDownloadErrors)
{ {
if (_launchClient != null) if (_launchClient != null)
{ {
return _launchClient.GetResolveActionsDownloadInfoAsync(planId, jobId, actionReferenceList, if (!displayHelpfulActionsDownloadErrors)
cancellationToken: cancellationToken); {
return _launchClient.GetResolveActionsDownloadInfoAsync(planId, jobId, actionReferenceList,
cancellationToken: cancellationToken);
}
return _launchClient.GetResolveActionsDownloadInfoAsyncV2(planId, jobId, actionReferenceList, cancellationToken);
} }
throw new InvalidOperationException("Launch client is not initialized."); throw new InvalidOperationException("Launch client is not initialized.");

View File

@@ -18,5 +18,22 @@ namespace GitHub.Runner.Common.Util
} }
return _defaultNodeVersion; return _defaultNodeVersion;
} }
/// <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, "node24", StringComparison.OrdinalIgnoreCase) &&
Constants.Runner.PlatformArchitecture.Equals(Constants.Architecture.Arm) &&
Constants.Runner.Platform.Equals(Constants.OSPlatform.Linux))
{
return ("node20", "Node 24 is not supported on Linux ARM32 platforms. Falling back to Node 20.");
}
return (preferredVersion, null);
}
} }
} }

View File

@@ -70,7 +70,7 @@ namespace GitHub.Runner.Listener.Configuration
public RunnerSettings LoadMigratedSettings() public RunnerSettings LoadMigratedSettings()
{ {
Trace.Info(nameof(LoadMigratedSettings)); Trace.Info(nameof(LoadMigratedSettings));
// Check if migrated settings file exists // Check if migrated settings file exists
if (!_store.IsMigratedConfigured()) if (!_store.IsMigratedConfigured())
{ {
@@ -387,6 +387,14 @@ namespace GitHub.Runner.Listener.Configuration
}, },
}; };
if (agent.Properties.GetValue("EnableAuthMigrationByDefault", false) &&
agent.Properties.TryGetValue<string>("AuthorizationUrlV2", out var authUrlV2) &&
!string.IsNullOrEmpty(authUrlV2))
{
credentialData.Data["enableAuthMigrationByDefault"] = "true";
credentialData.Data["authorizationUrlV2"] = authUrlV2;
}
// Save the negotiated OAuth credential data // Save the negotiated OAuth credential data
_store.SaveCredential(credentialData); _store.SaveCredential(credentialData);
} }

View File

@@ -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");

View File

@@ -38,6 +38,7 @@ namespace GitHub.Runner.Sdk
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY"))) if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY")))
{ {
VssClientHttpRequestSettings.Default.ServerCertificateValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; VssClientHttpRequestSettings.Default.ServerCertificateValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
RawClientHttpRequestSettings.Default.ServerCertificateValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
} }
var rawHeaderValues = new List<ProductInfoHeaderValue>(); var rawHeaderValues = new List<ProductInfoHeaderValue>();

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<configuration> <configuration>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7" />
</startup> </startup>
</configuration> </configuration>

View File

@@ -18,7 +18,7 @@
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion> <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(PackageRuntime)' != 'win-arm64' "> <PropertyGroup Condition=" '$(PackageRuntime)' != 'win-arm64' ">
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>

View File

@@ -688,7 +688,8 @@ namespace GitHub.Runner.Worker
{ {
if (MessageUtil.IsRunServiceJob(executionContext.Global.Variables.Get(Constants.Variables.System.JobRequestType))) if (MessageUtil.IsRunServiceJob(executionContext.Global.Variables.Get(Constants.Variables.System.JobRequestType)))
{ {
actionDownloadInfos = await launchServer.ResolveActionsDownloadInfoAsync(executionContext.Global.Plan.PlanId, executionContext.Root.Id, new WebApi.ActionReferenceList { Actions = actionReferences }, executionContext.CancellationToken); var displayHelpfulActionsDownloadErrors = executionContext.Global.Variables.GetBoolean(Constants.Runner.Features.DisplayHelpfulActionsDownloadErrors) ?? false;
actionDownloadInfos = await launchServer.ResolveActionsDownloadInfoAsync(executionContext.Global.Plan.PlanId, executionContext.Root.Id, new WebApi.ActionReferenceList { Actions = actionReferences }, executionContext.CancellationToken, displayHelpfulActionsDownloadErrors);
} }
else else
{ {

View File

@@ -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(

View File

@@ -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();

View File

@@ -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);

View File

@@ -106,6 +106,18 @@ namespace GitHub.Services.Common
{ {
VssTraceActivity traceActivity = VssTraceActivity.Current; VssTraceActivity traceActivity = VssTraceActivity.Current;
if (!m_appliedServerCertificateValidationCallbackToTransportHandler &&
request.RequestUri.Scheme == "https")
{
HttpClientHandler httpClientHandler = m_transportHandler as HttpClientHandler;
if (httpClientHandler != null &&
this.Settings.ServerCertificateValidationCallback != null)
{
httpClientHandler.ServerCertificateCustomValidationCallback = this.Settings.ServerCertificateValidationCallback;
}
m_appliedServerCertificateValidationCallbackToTransportHandler = true;
}
lock (m_thisLock) lock (m_thisLock)
{ {
// Ensure that we attempt to use the most appropriate authentication mechanism by default. // Ensure that we attempt to use the most appropriate authentication mechanism by default.
@@ -291,6 +303,7 @@ namespace GitHub.Services.Common
} }
} }
private bool m_appliedServerCertificateValidationCallbackToTransportHandler;
private readonly HttpMessageHandler m_transportHandler; private readonly HttpMessageHandler m_transportHandler;
private HttpMessageInvoker m_messageInvoker; private HttpMessageInvoker m_messageInvoker;
private CredentialWrapper m_credentialWrapper; private CredentialWrapper m_credentialWrapper;

View File

@@ -29,7 +29,7 @@ namespace GitHub.Services.Launch.Contracts
{ {
[DataMember(EmitDefaultValue = false, Name = "authentication")] [DataMember(EmitDefaultValue = false, Name = "authentication")]
public ActionDownloadAuthenticationResponse Authentication { get; set; } public ActionDownloadAuthenticationResponse Authentication { get; set; }
[DataMember(EmitDefaultValue = false, Name = "package_details")] [DataMember(EmitDefaultValue = false, Name = "package_details")]
public ActionDownloadPackageDetailsResponse PackageDetails { get; set; } public ActionDownloadPackageDetailsResponse PackageDetails { get; set; }
@@ -64,7 +64,7 @@ namespace GitHub.Services.Launch.Contracts
[DataContract] [DataContract]
public class ActionDownloadPackageDetailsResponse public class ActionDownloadPackageDetailsResponse
{ {
[DataMember(EmitDefaultValue = false, Name = "version")] [DataMember(EmitDefaultValue = false, Name = "version")]
public string Version { get; set; } public string Version { get; set; }
@@ -81,4 +81,25 @@ namespace GitHub.Services.Launch.Contracts
[DataMember(EmitDefaultValue = false, Name = "actions")] [DataMember(EmitDefaultValue = false, Name = "actions")]
public IDictionary<string, ActionDownloadInfoResponse> Actions { get; set; } public IDictionary<string, ActionDownloadInfoResponse> Actions { get; set; }
} }
[DataContract]
public class ActionDownloadResolutionError
{
/// <summary>
/// The error message associated with the action download error.
/// </summary>
[DataMember(EmitDefaultValue = false, Name = "message")]
public string Message { get; set; }
}
[DataContract]
public class ActionDownloadResolutionErrorCollection
{
/// <summary>
/// A mapping of action specifications to their download errors.
/// <remarks>The key is the full name of the action plus version, e.g. "actions/checkout@v2".</remarks>
/// </summary>
[DataMember(EmitDefaultValue = false, Name = "errors")]
public IDictionary<string, ActionDownloadResolutionError> Errors { get; set; }
}
} }

View File

@@ -2,6 +2,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Formatting; using System.Net.Http.Formatting;
using System.Net.Http.Headers; using System.Net.Http.Headers;
@@ -32,11 +33,52 @@ namespace GitHub.Services.Launch.Client
public async Task<ActionDownloadInfoCollection> GetResolveActionsDownloadInfoAsync(Guid planId, Guid jobId, ActionReferenceList actionReferenceList, CancellationToken cancellationToken) public async Task<ActionDownloadInfoCollection> GetResolveActionsDownloadInfoAsync(Guid planId, Guid jobId, ActionReferenceList actionReferenceList, CancellationToken cancellationToken)
{ {
var GetResolveActionsDownloadInfoURLEndpoint = new Uri(m_launchServiceUrl, $"/actions/build/{planId.ToString()}/jobs/{jobId.ToString()}/runnerresolve/actions"); var GetResolveActionsDownloadInfoURLEndpoint = new Uri(m_launchServiceUrl, $"/actions/build/{planId.ToString()}/jobs/{jobId.ToString()}/runnerresolve/actions");
return ToServerData(await GetLaunchSignedURLResponse<ActionReferenceRequestList, ActionDownloadInfoResponseCollection>(GetResolveActionsDownloadInfoURLEndpoint, ToGitHubData(actionReferenceList), cancellationToken)); var response = await GetLaunchSignedURLResponse<ActionReferenceRequestList>(GetResolveActionsDownloadInfoURLEndpoint, ToGitHubData(actionReferenceList), cancellationToken);
return ToServerData(await ReadJsonContentAsync<ActionDownloadInfoResponseCollection>(response, cancellationToken));
} }
// Resolve Actions public async Task<ActionDownloadInfoCollection> GetResolveActionsDownloadInfoAsyncV2(Guid planId, Guid jobId, ActionReferenceList actionReferenceList, CancellationToken cancellationToken)
private async Task<T> GetLaunchSignedURLResponse<R, T>(Uri uri, R request, CancellationToken cancellationToken) {
var GetResolveActionsDownloadInfoURLEndpoint = new Uri(m_launchServiceUrl, $"/actions/build/{planId.ToString()}/jobs/{jobId.ToString()}/runnerresolve/actions");
var response = await GetLaunchSignedURLResponse<ActionReferenceRequestList>(GetResolveActionsDownloadInfoURLEndpoint, ToGitHubData(actionReferenceList), cancellationToken);
if (response.IsSuccessStatusCode)
{
// Success response - deserialize the action download info
return ToServerData(await ReadJsonContentAsync<ActionDownloadInfoResponseCollection>(response, cancellationToken));
}
var responseError = response.ReasonPhrase ?? "";
if (response.StatusCode == HttpStatusCode.UnprocessableEntity)
{
// 422 response - unresolvable actions, error details are in the body
var errors = await ReadJsonContentAsync<ActionDownloadResolutionErrorCollection>(response, cancellationToken);
string combinedErrorMessage;
if (errors?.Errors != null && errors.Errors.Any())
{
combinedErrorMessage = String.Join(". ", errors.Errors.Select(kvp => kvp.Value.Message));
}
else
{
combinedErrorMessage = responseError;
}
throw new UnresolvableActionDownloadInfoException(combinedErrorMessage);
}
else if (response.StatusCode == HttpStatusCode.TooManyRequests)
{
// Here we want to add a message so customers don't think it's a rate limit scoped to them
// Ideally this would be 500 but the runner retries 500s, which we don't want to do when we're being rate limited
// See: https://github.com/github/ecosystem-api/issues/4084
throw new NonRetryableActionDownloadInfoException(responseError + " (GitHub has reached an internal rate limit, please try again later)");
}
else
{
throw new Exception(responseError);
}
}
private async Task<HttpResponseMessage> GetLaunchSignedURLResponse<R>(Uri uri, R request, CancellationToken cancellationToken)
{ {
using (HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, uri)) using (HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, uri))
{ {
@@ -46,10 +88,7 @@ namespace GitHub.Services.Launch.Client
using (HttpContent content = new ObjectContent<R>(request, m_formatter)) using (HttpContent content = new ObjectContent<R>(request, m_formatter))
{ {
requestMessage.Content = content; requestMessage.Content = content;
using (var response = await SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationToken: cancellationToken)) return await SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationToken: cancellationToken);
{
return await ReadJsonContentAsync<T>(response, cancellationToken);
}
} }
} }
} }

View File

@@ -520,8 +520,8 @@ namespace GitHub.Services.Results.Client
Number = r.Order.GetValueOrDefault(), Number = r.Order.GetValueOrDefault(),
Name = r.Name, Name = r.Name,
Status = ConvertStateToStatus(r.State.GetValueOrDefault()), Status = ConvertStateToStatus(r.State.GetValueOrDefault()),
StartedAt = r.StartTime?.ToString(Constants.TimestampFormat), StartedAt = r.StartTime?.ToString(Constants.TimestampFormat, CultureInfo.InvariantCulture),
CompletedAt = r.FinishTime?.ToString(Constants.TimestampFormat), CompletedAt = r.FinishTime?.ToString(Constants.TimestampFormat, CultureInfo.InvariantCulture),
Conclusion = ConvertResultToConclusion(r.Result) Conclusion = ConvertResultToConclusion(r.Result)
}; };
} }

View File

@@ -0,0 +1,126 @@
using GitHub.Actions.RunService.WebApi;
using GitHub.DistributedTask.WebApi;
using GitHub.Services.Launch.Client;
using GitHub.Services.Launch.Contracts;
using Moq;
using Moq.Protected;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace GitHub.Actions.RunService.WebApi.Tests
{
public sealed class LaunchHttpClientL0
{
[Fact]
public async Task GetResolveActionsDownloadInfoAsync_SuccessResponse()
{
var baseUrl = new Uri("https://api.github.com/");
var planId = Guid.NewGuid();
var jobId = Guid.NewGuid();
var token = "fake-token";
var actionReferenceList = new ActionReferenceList
{
Actions = new List<ActionReference>
{
new ActionReference
{
NameWithOwner = "owner1/action1",
Ref = "0123456789"
}
}
};
var responseContent = @"{
""actions"": {
""owner1/action1@0123456789"": {
""name"": ""owner1/action1"",
""resolved_name"": ""owner1/action1"",
""resolved_sha"": ""0123456789"",
""version"": ""0123456789"",
""zip_url"": ""https://github.com/owner1/action1/zip"",
""tar_url"": ""https://github.com/owner1/action1/tar""
}
}
}";
var httpResponse = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(responseContent, Encoding.UTF8, "application/json"),
RequestMessage = new HttpRequestMessage()
{
RequestUri = new Uri($"{baseUrl}actions/build/{planId}/jobs/{jobId}/runnerresolve/actions")
}
};
var mockHandler = new Mock<HttpMessageHandler>();
mockHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(httpResponse);
var client = new LaunchHttpClient(baseUrl, mockHandler.Object, token, false);
var result = await client.GetResolveActionsDownloadInfoAsyncV2(planId, jobId, actionReferenceList, CancellationToken.None);
// Assert
Assert.NotNull(result);
Assert.NotEmpty(result.Actions);
Assert.Equal(actionReferenceList.Actions.Count, result.Actions.Count);
Assert.True(result.Actions.ContainsKey("owner1/action1@0123456789"));
}
[Fact]
public async Task GetResolveActionsDownloadInfoAsync_UnprocessableEntityResponse()
{
var baseUrl = new Uri("https://api.github.com/");
var planId = Guid.NewGuid();
var jobId = Guid.NewGuid();
var token = "fake-token";
var actionReferenceList = new ActionReferenceList
{
Actions = new List<ActionReference>
{
new ActionReference
{
NameWithOwner = "owner1/action1",
Ref = "0123456789"
}
}
};
var responseContent = @"{
""errors"": {
""owner1/invalid-action@0123456789"": {
""message"": ""Unable to resolve action 'owner1/invalid-action@0123456789', repository not found""
}
}
}";
var httpResponse = new HttpResponseMessage(HttpStatusCode.UnprocessableEntity)
{
Content = new StringContent(responseContent, Encoding.UTF8, "application/json"),
RequestMessage = new HttpRequestMessage()
{
RequestUri = new Uri($"{baseUrl}actions/build/{planId}/jobs/{jobId}/runnerresolve/actions")
}
};
var mockHandler = new Mock<HttpMessageHandler>();
mockHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(httpResponse);
var client = new LaunchHttpClient(baseUrl, mockHandler.Object, token, false);
var exception = await Assert.ThrowsAsync<UnresolvableActionDownloadInfoException>(
() => client.GetResolveActionsDownloadInfoAsyncV2(planId, jobId, actionReferenceList, CancellationToken.None));
Assert.Contains("repository not found", exception.Message);
}
}
}

View File

@@ -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")]
@@ -2411,8 +2481,8 @@ runs:
}); });
_launchServer = new Mock<ILaunchServer>(); _launchServer = new Mock<ILaunchServer>();
_launchServer.Setup(x => x.ResolveActionsDownloadInfoAsync(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<ActionReferenceList>(), It.IsAny<CancellationToken>())) _launchServer.Setup(x => x.ResolveActionsDownloadInfoAsync(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<ActionReferenceList>(), It.IsAny<CancellationToken>(), It.IsAny<bool>()))
.Returns((Guid planId, Guid jobId, ActionReferenceList actions, CancellationToken cancellationToken) => .Returns((Guid planId, Guid jobId, ActionReferenceList actions, CancellationToken cancellationToken, bool displayHelpfulActionsDownloadErrors) =>
{ {
var result = new ActionDownloadInfoCollection { Actions = new Dictionary<string, ActionDownloadInfo>() }; var result = new ActionDownloadInfoCollection { Actions = new Dictionary<string, ActionDownloadInfo>() };
foreach (var action in actions.Actions) foreach (var action in actions.Actions)

View File

@@ -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
{ {

View File

@@ -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);
}
}
} }
} }

View 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);
}
}
}

View File

@@ -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
} }
} }

View 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);
}
}
}

View 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'

View File

@@ -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.408" DOTNETSDK_VERSION="8.0.412"
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION" DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
RUNNER_VERSION=$(cat runnerversion) RUNNER_VERSION=$(cat runnerversion)

View File

@@ -1,5 +1,5 @@
{ {
"sdk": { "sdk": {
"version": "8.0.408" "version": "8.0.412"
} }
} }

View File

@@ -1 +1 @@
2.324.0 2.327.1